diff options
Diffstat (limited to 'src')
601 files changed, 23288 insertions, 9342 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2da983cad..d7f68618c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -24,7 +24,7 @@ if (MSVC) | |||
| 24 | # Ensure that projects build with Unicode support. | 24 | # Ensure that projects build with Unicode support. |
| 25 | add_definitions(-DUNICODE -D_UNICODE) | 25 | add_definitions(-DUNICODE -D_UNICODE) |
| 26 | 26 | ||
| 27 | # /W3 - Level 3 warnings | 27 | # /W4 - Level 4 warnings |
| 28 | # /MP - Multi-threaded compilation | 28 | # /MP - Multi-threaded compilation |
| 29 | # /Zi - Output debugging information | 29 | # /Zi - Output debugging information |
| 30 | # /Zm - Specifies the precompiled header memory allocation limit | 30 | # /Zm - Specifies the precompiled header memory allocation limit |
| @@ -61,7 +61,7 @@ if (MSVC) | |||
| 61 | /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers | 61 | /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers |
| 62 | 62 | ||
| 63 | # Warnings | 63 | # Warnings |
| 64 | /W3 | 64 | /W4 |
| 65 | /WX | 65 | /WX |
| 66 | 66 | ||
| 67 | /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled | 67 | /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled |
| @@ -84,12 +84,17 @@ if (MSVC) | |||
| 84 | 84 | ||
| 85 | /wd4100 # 'identifier': unreferenced formal parameter | 85 | /wd4100 # 'identifier': unreferenced formal parameter |
| 86 | /wd4324 # 'struct_name': structure was padded due to __declspec(align()) | 86 | /wd4324 # 'struct_name': structure was padded due to __declspec(align()) |
| 87 | /wd4201 # nonstandard extension used : nameless struct/union | ||
| 88 | /wd4702 # unreachable code (when used with LTO) | ||
| 87 | ) | 89 | ) |
| 88 | 90 | ||
| 89 | if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) | 91 | if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) |
| 90 | # when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format | 92 | # when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format |
| 91 | # Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21 | 93 | # Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21 |
| 92 | add_compile_options(/Z7) | 94 | add_compile_options(/Z7) |
| 95 | # Avoid D9025 warning | ||
| 96 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") | ||
| 97 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") | ||
| 93 | else() | 98 | else() |
| 94 | add_compile_options(/Zi) | 99 | add_compile_options(/Zi) |
| 95 | endif() | 100 | endif() |
| @@ -105,6 +110,8 @@ if (MSVC) | |||
| 105 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) | 110 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) |
| 106 | else() | 111 | else() |
| 107 | add_compile_options( | 112 | add_compile_options( |
| 113 | -fwrapv | ||
| 114 | |||
| 108 | -Werror=all | 115 | -Werror=all |
| 109 | -Werror=extra | 116 | -Werror=extra |
| 110 | -Werror=missing-declarations | 117 | -Werror=missing-declarations |
| @@ -114,19 +121,21 @@ else() | |||
| 114 | -Wno-attributes | 121 | -Wno-attributes |
| 115 | -Wno-invalid-offsetof | 122 | -Wno-invalid-offsetof |
| 116 | -Wno-unused-parameter | 123 | -Wno-unused-parameter |
| 117 | |||
| 118 | $<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init> | ||
| 119 | $<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field> | ||
| 120 | $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local> | ||
| 121 | $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough> | ||
| 122 | $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits> | ||
| 123 | $<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init> | ||
| 124 | $<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field> | ||
| 125 | ) | 124 | ) |
| 126 | 125 | ||
| 126 | if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang | ||
| 127 | add_compile_options( | ||
| 128 | -Wno-braced-scalar-init | ||
| 129 | -Wno-unused-private-field | ||
| 130 | -Wno-nullability-completeness | ||
| 131 | -Werror=shadow-uncaptured-local | ||
| 132 | -Werror=implicit-fallthrough | ||
| 133 | -Werror=type-limits | ||
| 134 | ) | ||
| 135 | endif() | ||
| 136 | |||
| 127 | if (ARCHITECTURE_x86_64) | 137 | if (ARCHITECTURE_x86_64) |
| 128 | add_compile_options("-mcx16") | 138 | add_compile_options("-mcx16") |
| 129 | add_compile_options("-fwrapv") | ||
| 130 | endif() | 139 | endif() |
| 131 | 140 | ||
| 132 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) | 141 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) |
| @@ -134,7 +143,7 @@ else() | |||
| 134 | endif() | 143 | endif() |
| 135 | 144 | ||
| 136 | # GCC bugs | 145 | # GCC bugs |
| 137 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") | 146 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") |
| 138 | # These diagnostics would be great if they worked, but are just completely broken | 147 | # These diagnostics would be great if they worked, but are just completely broken |
| 139 | # and produce bogus errors on external libraries like fmt. | 148 | # and produce bogus errors on external libraries like fmt. |
| 140 | add_compile_options( | 149 | add_compile_options( |
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 9a47e2bd8..431f899b3 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -77,13 +77,30 @@ android { | |||
| 77 | buildConfigField("String", "BRANCH", "\"${getBranch()}\"") | 77 | buildConfigField("String", "BRANCH", "\"${getBranch()}\"") |
| 78 | } | 78 | } |
| 79 | 79 | ||
| 80 | val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE") | ||
| 81 | if (keystoreFile != null) { | ||
| 82 | signingConfigs { | ||
| 83 | create("release") { | ||
| 84 | storeFile = file(keystoreFile) | ||
| 85 | storePassword = System.getenv("ANDROID_KEYSTORE_PASS") | ||
| 86 | keyAlias = System.getenv("ANDROID_KEY_ALIAS") | ||
| 87 | keyPassword = System.getenv("ANDROID_KEYSTORE_PASS") | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 80 | // Define build types, which are orthogonal to product flavors. | 92 | // Define build types, which are orthogonal to product flavors. |
| 81 | buildTypes { | 93 | buildTypes { |
| 82 | 94 | ||
| 83 | // Signed by release key, allowing for upload to Play Store. | 95 | // Signed by release key, allowing for upload to Play Store. |
| 84 | release { | 96 | release { |
| 97 | signingConfig = if (keystoreFile != null) { | ||
| 98 | signingConfigs.getByName("release") | ||
| 99 | } else { | ||
| 100 | signingConfigs.getByName("debug") | ||
| 101 | } | ||
| 102 | |||
| 85 | resValue("string", "app_name_suffixed", "yuzu") | 103 | resValue("string", "app_name_suffixed", "yuzu") |
| 86 | signingConfig = signingConfigs.getByName("debug") | ||
| 87 | isMinifyEnabled = true | 104 | isMinifyEnabled = true |
| 88 | isDebuggable = false | 105 | isDebuggable = false |
| 89 | proguardFiles( | 106 | proguardFiles( |
| @@ -95,6 +112,7 @@ android { | |||
| 95 | // builds a release build that doesn't need signing | 112 | // builds a release build that doesn't need signing |
| 96 | // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. | 113 | // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. |
| 97 | register("relWithDebInfo") { | 114 | register("relWithDebInfo") { |
| 115 | isDefault = true | ||
| 98 | resValue("string", "app_name_suffixed", "yuzu Debug Release") | 116 | resValue("string", "app_name_suffixed", "yuzu Debug Release") |
| 99 | signingConfig = signingConfigs.getByName("debug") | 117 | signingConfig = signingConfigs.getByName("debug") |
| 100 | isMinifyEnabled = true | 118 | isMinifyEnabled = true |
| @@ -122,6 +140,7 @@ android { | |||
| 122 | flavorDimensions.add("version") | 140 | flavorDimensions.add("version") |
| 123 | productFlavors { | 141 | productFlavors { |
| 124 | create("mainline") { | 142 | create("mainline") { |
| 143 | isDefault = true | ||
| 125 | dimension = "version" | 144 | dimension = "version" |
| 126 | buildConfigField("Boolean", "PREMIUM", "false") | 145 | buildConfigField("Boolean", "PREMIUM", "false") |
| 127 | } | 146 | } |
| @@ -160,6 +179,11 @@ android { | |||
| 160 | } | 179 | } |
| 161 | } | 180 | } |
| 162 | 181 | ||
| 182 | tasks.create<Delete>("ktlintReset") { | ||
| 183 | delete(File(buildDir.path + File.separator + "intermediates/ktLint")) | ||
| 184 | } | ||
| 185 | |||
| 186 | tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset") | ||
| 163 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") | 187 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") |
| 164 | 188 | ||
| 165 | ktlint { | 189 | ktlint { |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 6184f3eb6..832c08e15 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -25,6 +25,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 25 | android:hasFragileUserData="false" | 25 | android:hasFragileUserData="false" |
| 26 | android:supportsRtl="true" | 26 | android:supportsRtl="true" |
| 27 | android:isGame="true" | 27 | android:isGame="true" |
| 28 | android:appCategory="game" | ||
| 28 | android:localeConfig="@xml/locales_config" | 29 | android:localeConfig="@xml/locales_config" |
| 29 | android:banner="@drawable/tv_banner" | 30 | android:banner="@drawable/tv_banner" |
| 30 | android:extractNativeLibs="true" | 31 | android:extractNativeLibs="true" |
| @@ -55,7 +56,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 55 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | 56 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" |
| 56 | android:theme="@style/Theme.Yuzu.Main" | 57 | android:theme="@style/Theme.Yuzu.Main" |
| 57 | android:launchMode="singleTop" | 58 | android:launchMode="singleTop" |
| 58 | android:screenOrientation="userLandscape" | ||
| 59 | android:supportsPictureInPicture="true" | 59 | android:supportsPictureInPicture="true" |
| 60 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" | 60 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" |
| 61 | android:exported="true"> | 61 | android:exported="true"> |
| @@ -66,6 +66,14 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 66 | <data android:mimeType="application/octet-stream" /> | 66 | <data android:mimeType="application/octet-stream" /> |
| 67 | </intent-filter> | 67 | </intent-filter> |
| 68 | 68 | ||
| 69 | <intent-filter> | ||
| 70 | <action android:name="android.intent.action.VIEW" /> | ||
| 71 | <category android:name="android.intent.category.DEFAULT" /> | ||
| 72 | <data | ||
| 73 | android:mimeType="application/octet-stream" | ||
| 74 | android:scheme="content"/> | ||
| 75 | </intent-filter> | ||
| 76 | |||
| 69 | <meta-data | 77 | <meta-data |
| 70 | android:name="android.nfc.action.TECH_DISCOVERED" | 78 | android:name="android.nfc.action.TECH_DISCOVERED" |
| 71 | android:resource="@xml/nfc_tech_filter" /> | 79 | android:resource="@xml/nfc_tech_filter" /> |
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 9c32e044c..21f67f32a 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 | |||
| @@ -22,9 +22,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil.exists | |||
| 22 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize | 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.FileUtil.openContentUri |
| 25 | import org.yuzu.yuzu_emu.utils.Log.error | 25 | import org.yuzu.yuzu_emu.utils.Log |
| 26 | import org.yuzu.yuzu_emu.utils.Log.verbose | ||
| 27 | import org.yuzu.yuzu_emu.utils.Log.warning | ||
| 28 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 26 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 29 | 27 | ||
| 30 | /** | 28 | /** |
| @@ -219,10 +217,6 @@ object NativeLibrary { | |||
| 219 | 217 | ||
| 220 | external fun reloadSettings() | 218 | external fun reloadSettings() |
| 221 | 219 | ||
| 222 | external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String? | ||
| 223 | |||
| 224 | external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?) | ||
| 225 | |||
| 226 | external fun initGameIni(gameID: String?) | 220 | external fun initGameIni(gameID: String?) |
| 227 | 221 | ||
| 228 | /** | 222 | /** |
| @@ -314,21 +308,6 @@ object NativeLibrary { | |||
| 314 | external fun isPaused(): Boolean | 308 | external fun isPaused(): Boolean |
| 315 | 309 | ||
| 316 | /** | 310 | /** |
| 317 | * Mutes emulation sound | ||
| 318 | */ | ||
| 319 | external fun muteAudio(): Boolean | ||
| 320 | |||
| 321 | /** | ||
| 322 | * Unmutes emulation sound | ||
| 323 | */ | ||
| 324 | external fun unmuteAudio(): Boolean | ||
| 325 | |||
| 326 | /** | ||
| 327 | * Returns true if emulation audio is muted. | ||
| 328 | */ | ||
| 329 | external fun isMuted(): Boolean | ||
| 330 | |||
| 331 | /** | ||
| 332 | * Returns the performance stats for the current game | 311 | * Returns the performance stats for the current game |
| 333 | */ | 312 | */ |
| 334 | external fun getPerfStats(): DoubleArray | 313 | external fun getPerfStats(): DoubleArray |
| @@ -413,14 +392,17 @@ object NativeLibrary { | |||
| 413 | details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } | 392 | details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } |
| 414 | ) | 393 | ) |
| 415 | } | 394 | } |
| 395 | |||
| 416 | CoreError.ErrorSavestate -> { | 396 | CoreError.ErrorSavestate -> { |
| 417 | title = emulationActivity.getString(R.string.save_load_error) | 397 | title = emulationActivity.getString(R.string.save_load_error) |
| 418 | message = details | 398 | message = details |
| 419 | } | 399 | } |
| 400 | |||
| 420 | CoreError.ErrorUnknown -> { | 401 | CoreError.ErrorUnknown -> { |
| 421 | title = emulationActivity.getString(R.string.fatal_error) | 402 | title = emulationActivity.getString(R.string.fatal_error) |
| 422 | message = emulationActivity.getString(R.string.fatal_error_message) | 403 | message = emulationActivity.getString(R.string.fatal_error_message) |
| 423 | } | 404 | } |
| 405 | |||
| 424 | else -> { | 406 | else -> { |
| 425 | return true | 407 | return true |
| 426 | } | 408 | } |
| @@ -454,6 +436,7 @@ object NativeLibrary { | |||
| 454 | captionId = R.string.loader_error_video_core | 436 | captionId = R.string.loader_error_video_core |
| 455 | descriptionId = R.string.loader_error_video_core_description | 437 | descriptionId = R.string.loader_error_video_core_description |
| 456 | } | 438 | } |
| 439 | |||
| 457 | else -> { | 440 | else -> { |
| 458 | captionId = R.string.loader_error_encrypted | 441 | captionId = R.string.loader_error_encrypted |
| 459 | descriptionId = R.string.loader_error_encrypted_roms_description | 442 | descriptionId = R.string.loader_error_encrypted_roms_description |
| @@ -465,7 +448,7 @@ object NativeLibrary { | |||
| 465 | 448 | ||
| 466 | val emulationActivity = sEmulationActivity.get() | 449 | val emulationActivity = sEmulationActivity.get() |
| 467 | if (emulationActivity == null) { | 450 | if (emulationActivity == null) { |
| 468 | warning("[NativeLibrary] EmulationActivity is null, can't exit.") | 451 | Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.") |
| 469 | return | 452 | return |
| 470 | } | 453 | } |
| 471 | 454 | ||
| @@ -490,15 +473,27 @@ object NativeLibrary { | |||
| 490 | } | 473 | } |
| 491 | 474 | ||
| 492 | fun setEmulationActivity(emulationActivity: EmulationActivity?) { | 475 | fun setEmulationActivity(emulationActivity: EmulationActivity?) { |
| 493 | verbose("[NativeLibrary] Registering EmulationActivity.") | 476 | Log.verbose("[NativeLibrary] Registering EmulationActivity.") |
| 494 | sEmulationActivity = WeakReference(emulationActivity) | 477 | sEmulationActivity = WeakReference(emulationActivity) |
| 495 | } | 478 | } |
| 496 | 479 | ||
| 497 | fun clearEmulationActivity() { | 480 | fun clearEmulationActivity() { |
| 498 | verbose("[NativeLibrary] Unregistering EmulationActivity.") | 481 | Log.verbose("[NativeLibrary] Unregistering EmulationActivity.") |
| 499 | sEmulationActivity.clear() | 482 | sEmulationActivity.clear() |
| 500 | } | 483 | } |
| 501 | 484 | ||
| 485 | @Keep | ||
| 486 | @JvmStatic | ||
| 487 | fun onEmulationStarted() { | ||
| 488 | sEmulationActivity.get()!!.onEmulationStarted() | ||
| 489 | } | ||
| 490 | |||
| 491 | @Keep | ||
| 492 | @JvmStatic | ||
| 493 | fun onEmulationStopped(status: Int) { | ||
| 494 | sEmulationActivity.get()!!.onEmulationStopped(status) | ||
| 495 | } | ||
| 496 | |||
| 502 | /** | 497 | /** |
| 503 | * Logs the Yuzu version, Android version and, CPU. | 498 | * Logs the Yuzu version, Android version and, CPU. |
| 504 | */ | 499 | */ |
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 04ab6a220..9561748cb 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 | |||
| @@ -46,7 +46,7 @@ class YuzuApplication : Application() { | |||
| 46 | super.onCreate() | 46 | super.onCreate() |
| 47 | application = this | 47 | application = this |
| 48 | documentsTree = DocumentsTree() | 48 | documentsTree = DocumentsTree() |
| 49 | DirectoryInitialization.start(applicationContext) | 49 | DirectoryInitialization.start() |
| 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) | 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) |
| 51 | NativeLibrary.logDeviceInfo() | 51 | NativeLibrary.logDeviceInfo() |
| 52 | 52 | ||
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 7461fb093..d4ae39661 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 | |||
| @@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | |||
| 42 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 42 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| 43 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 43 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 44 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 44 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 45 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | 45 | import org.yuzu.yuzu_emu.model.EmulationViewModel |
| 46 | import org.yuzu.yuzu_emu.model.Game | 46 | import org.yuzu.yuzu_emu.model.Game |
| 47 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper | 47 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper |
| 48 | import org.yuzu.yuzu_emu.utils.ForegroundService | 48 | import org.yuzu.yuzu_emu.utils.ForegroundService |
| @@ -72,18 +72,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 72 | private val actionMute = "ACTION_EMULATOR_MUTE" | 72 | private val actionMute = "ACTION_EMULATOR_MUTE" |
| 73 | private val actionUnmute = "ACTION_EMULATOR_UNMUTE" | 73 | private val actionUnmute = "ACTION_EMULATOR_UNMUTE" |
| 74 | 74 | ||
| 75 | private val settingsViewModel: SettingsViewModel by viewModels() | 75 | private val emulationViewModel: EmulationViewModel by viewModels() |
| 76 | 76 | ||
| 77 | override fun onDestroy() { | 77 | override fun onDestroy() { |
| 78 | stopForegroundService(this) | 78 | stopForegroundService(this) |
| 79 | emulationViewModel.clear() | ||
| 79 | super.onDestroy() | 80 | super.onDestroy() |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | override fun onCreate(savedInstanceState: Bundle?) { | 83 | override fun onCreate(savedInstanceState: Bundle?) { |
| 83 | ThemeHelper.setTheme(this) | 84 | ThemeHelper.setTheme(this) |
| 84 | 85 | ||
| 85 | settingsViewModel.settings.loadSettings() | ||
| 86 | |||
| 87 | super.onCreate(savedInstanceState) | 86 | super.onCreate(savedInstanceState) |
| 88 | 87 | ||
| 89 | binding = ActivityEmulationBinding.inflate(layoutInflater) | 88 | binding = ActivityEmulationBinding.inflate(layoutInflater) |
| @@ -91,9 +90,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 91 | 90 | ||
| 92 | val navHostFragment = | 91 | val navHostFragment = |
| 93 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment | 92 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 94 | val navController = navHostFragment.navController | 93 | navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras) |
| 95 | navController | ||
| 96 | .setGraph(R.navigation.emulation_navigation, intent.extras) | ||
| 97 | 94 | ||
| 98 | isActivityRecreated = savedInstanceState != null | 95 | isActivityRecreated = savedInstanceState != null |
| 99 | 96 | ||
| @@ -335,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 335 | pictureInPictureActions.add(pauseRemoteAction) | 332 | pictureInPictureActions.add(pauseRemoteAction) |
| 336 | } | 333 | } |
| 337 | 334 | ||
| 338 | if (NativeLibrary.isMuted()) { | 335 | if (BooleanSetting.AUDIO_MUTED.boolean) { |
| 339 | val unmuteIcon = Icon.createWithResource( | 336 | val unmuteIcon = Icon.createWithResource( |
| 340 | this@EmulationActivity, | 337 | this@EmulationActivity, |
| 341 | R.drawable.ic_pip_unmute | 338 | R.drawable.ic_pip_unmute |
| @@ -392,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 392 | if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() | 389 | if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() |
| 393 | } | 390 | } |
| 394 | if (intent.action == actionUnmute) { | 391 | if (intent.action == actionUnmute) { |
| 395 | if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() | 392 | if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false) |
| 396 | } else if (intent.action == actionMute) { | 393 | } else if (intent.action == actionMute) { |
| 397 | if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio() | 394 | if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true) |
| 398 | } | 395 | } |
| 399 | buildPictureInPictureParams() | 396 | buildPictureInPictureParams() |
| 400 | } | 397 | } |
| @@ -420,7 +417,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 420 | } catch (ignored: Exception) { | 417 | } catch (ignored: Exception) { |
| 421 | } | 418 | } |
| 422 | // Always resume audio, since there is no UI button | 419 | // Always resume audio, since there is no UI button |
| 423 | if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() | 420 | if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false) |
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | fun onEmulationStarted() { | ||
| 425 | emulationViewModel.setEmulationStarted(true) | ||
| 426 | } | ||
| 427 | |||
| 428 | fun onEmulationStopped(status: Int) { | ||
| 429 | if (status == 0) { | ||
| 430 | finish() | ||
| 424 | } | 431 | } |
| 425 | } | 432 | } |
| 426 | 433 | ||
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 e91277d35..f9f88a1d2 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 | |||
| @@ -3,8 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
| 5 | 5 | ||
| 6 | import android.content.Intent | ||
| 6 | import android.graphics.Bitmap | 7 | import android.graphics.Bitmap |
| 7 | import android.graphics.BitmapFactory | 8 | import android.graphics.drawable.LayerDrawable |
| 8 | import android.net.Uri | 9 | import android.net.Uri |
| 9 | import android.text.TextUtils | 10 | import android.text.TextUtils |
| 10 | import android.view.LayoutInflater | 11 | import android.view.LayoutInflater |
| @@ -13,25 +14,29 @@ import android.view.ViewGroup | |||
| 13 | import android.widget.ImageView | 14 | import android.widget.ImageView |
| 14 | import android.widget.Toast | 15 | import android.widget.Toast |
| 15 | import androidx.appcompat.app.AppCompatActivity | 16 | import androidx.appcompat.app.AppCompatActivity |
| 17 | import androidx.core.content.pm.ShortcutInfoCompat | ||
| 18 | import androidx.core.content.pm.ShortcutManagerCompat | ||
| 19 | import androidx.core.content.res.ResourcesCompat | ||
| 20 | import androidx.core.graphics.drawable.IconCompat | ||
| 21 | import androidx.core.graphics.drawable.toBitmap | ||
| 22 | import androidx.core.graphics.drawable.toDrawable | ||
| 16 | import androidx.documentfile.provider.DocumentFile | 23 | import androidx.documentfile.provider.DocumentFile |
| 17 | import androidx.lifecycle.ViewModelProvider | 24 | import androidx.lifecycle.ViewModelProvider |
| 18 | import androidx.lifecycle.lifecycleScope | ||
| 19 | import androidx.navigation.findNavController | 25 | import androidx.navigation.findNavController |
| 20 | import androidx.preference.PreferenceManager | 26 | import androidx.preference.PreferenceManager |
| 21 | import androidx.recyclerview.widget.AsyncDifferConfig | 27 | import androidx.recyclerview.widget.AsyncDifferConfig |
| 22 | import androidx.recyclerview.widget.DiffUtil | 28 | import androidx.recyclerview.widget.DiffUtil |
| 23 | import androidx.recyclerview.widget.ListAdapter | 29 | import androidx.recyclerview.widget.ListAdapter |
| 24 | import androidx.recyclerview.widget.RecyclerView | 30 | import androidx.recyclerview.widget.RecyclerView |
| 25 | import coil.load | ||
| 26 | import kotlinx.coroutines.launch | ||
| 27 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 31 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| 28 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 29 | import org.yuzu.yuzu_emu.R | 32 | import org.yuzu.yuzu_emu.R |
| 30 | import org.yuzu.yuzu_emu.YuzuApplication | 33 | import org.yuzu.yuzu_emu.YuzuApplication |
| 34 | import org.yuzu.yuzu_emu.activities.EmulationActivity | ||
| 31 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | 35 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder |
| 32 | import org.yuzu.yuzu_emu.databinding.CardGameBinding | 36 | import org.yuzu.yuzu_emu.databinding.CardGameBinding |
| 33 | import org.yuzu.yuzu_emu.model.Game | 37 | import org.yuzu.yuzu_emu.model.Game |
| 34 | import org.yuzu.yuzu_emu.model.GamesViewModel | 38 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 39 | import org.yuzu.yuzu_emu.utils.GameIconUtils | ||
| 35 | 40 | ||
| 36 | class GameAdapter(private val activity: AppCompatActivity) : | 41 | class GameAdapter(private val activity: AppCompatActivity) : |
| 37 | ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), | 42 | ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), |
| @@ -82,6 +87,34 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 82 | ) | 87 | ) |
| 83 | .apply() | 88 | .apply() |
| 84 | 89 | ||
| 90 | val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { | ||
| 91 | action = Intent.ACTION_VIEW | ||
| 92 | data = Uri.parse(holder.game.path) | ||
| 93 | } | ||
| 94 | |||
| 95 | val layerDrawable = ResourcesCompat.getDrawable( | ||
| 96 | YuzuApplication.appContext.resources, | ||
| 97 | R.drawable.shortcut, | ||
| 98 | null | ||
| 99 | ) as LayerDrawable | ||
| 100 | layerDrawable.setDrawableByLayerId( | ||
| 101 | R.id.shortcut_foreground, | ||
| 102 | GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources) | ||
| 103 | ) | ||
| 104 | val inset = YuzuApplication.appContext.resources | ||
| 105 | .getDimensionPixelSize(R.dimen.icon_inset) | ||
| 106 | layerDrawable.setLayerInset(1, inset, inset, inset, inset) | ||
| 107 | val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path) | ||
| 108 | .setShortLabel(holder.game.title) | ||
| 109 | .setIcon( | ||
| 110 | IconCompat.createWithAdaptiveBitmap( | ||
| 111 | layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888) | ||
| 112 | ) | ||
| 113 | ) | ||
| 114 | .setIntent(openIntent) | ||
| 115 | .build() | ||
| 116 | ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) | ||
| 117 | |||
| 85 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) | 118 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) |
| 86 | view.findNavController().navigate(action) | 119 | view.findNavController().navigate(action) |
| 87 | } | 120 | } |
| @@ -98,12 +131,7 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 98 | this.game = game | 131 | this.game = game |
| 99 | 132 | ||
| 100 | binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP | 133 | binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP |
| 101 | activity.lifecycleScope.launch { | 134 | GameIconUtils.loadGameIcon(game, binding.imageGameScreen) |
| 102 | val bitmap = decodeGameIcon(game.path) | ||
| 103 | binding.imageGameScreen.load(bitmap) { | ||
| 104 | error(R.drawable.default_icon) | ||
| 105 | } | ||
| 106 | } | ||
| 107 | 135 | ||
| 108 | binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ") | 136 | binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ") |
| 109 | 137 | ||
| @@ -126,14 +154,4 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 126 | return oldItem == newItem | 154 | return oldItem == newItem |
| 127 | } | 155 | } |
| 128 | } | 156 | } |
| 129 | |||
| 130 | private fun decodeGameIcon(uri: String): Bitmap? { | ||
| 131 | val data = NativeLibrary.getIcon(uri) | ||
| 132 | return BitmapFactory.decodeByteArray( | ||
| 133 | data, | ||
| 134 | 0, | ||
| 135 | data.size, | ||
| 136 | BitmapFactory.Options() | ||
| 137 | ) | ||
| 138 | } | ||
| 139 | } | 157 | } |
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 aadc445f9..1675627a1 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 | |||
| @@ -3,19 +3,29 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
| 5 | 5 | ||
| 6 | import android.text.TextUtils | ||
| 6 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
| 7 | import android.view.View | 8 | import android.view.View |
| 8 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 9 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.core.content.ContextCompat | 11 | import androidx.core.content.ContextCompat |
| 11 | import androidx.core.content.res.ResourcesCompat | 12 | import androidx.core.content.res.ResourcesCompat |
| 13 | import androidx.lifecycle.Lifecycle | ||
| 14 | import androidx.lifecycle.LifecycleOwner | ||
| 15 | import androidx.lifecycle.lifecycleScope | ||
| 16 | import androidx.lifecycle.repeatOnLifecycle | ||
| 12 | import androidx.recyclerview.widget.RecyclerView | 17 | import androidx.recyclerview.widget.RecyclerView |
| 18 | import kotlinx.coroutines.launch | ||
| 13 | import org.yuzu.yuzu_emu.R | 19 | import org.yuzu.yuzu_emu.R |
| 14 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding | 20 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding |
| 15 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 21 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 16 | import org.yuzu.yuzu_emu.model.HomeSetting | 22 | import org.yuzu.yuzu_emu.model.HomeSetting |
| 17 | 23 | ||
| 18 | class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) : | 24 | class HomeSettingAdapter( |
| 25 | private val activity: AppCompatActivity, | ||
| 26 | private val viewLifecycle: LifecycleOwner, | ||
| 27 | var options: List<HomeSetting> | ||
| 28 | ) : | ||
| 19 | RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), | 29 | RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), |
| 20 | View.OnClickListener { | 30 | View.OnClickListener { |
| 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { | 31 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { |
| @@ -39,8 +49,8 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L | |||
| 39 | holder.option.onClick.invoke() | 49 | holder.option.onClick.invoke() |
| 40 | } else { | 50 | } else { |
| 41 | MessageDialogFragment.newInstance( | 51 | MessageDialogFragment.newInstance( |
| 42 | holder.option.disabledTitleId, | 52 | titleId = holder.option.disabledTitleId, |
| 43 | holder.option.disabledMessageId | 53 | descriptionId = holder.option.disabledMessageId |
| 44 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | 54 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
| 45 | } | 55 | } |
| 46 | } | 56 | } |
| @@ -79,6 +89,26 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L | |||
| 79 | binding.optionDescription.alpha = 0.5f | 89 | binding.optionDescription.alpha = 0.5f |
| 80 | binding.optionIcon.alpha = 0.5f | 90 | binding.optionIcon.alpha = 0.5f |
| 81 | } | 91 | } |
| 92 | |||
| 93 | viewLifecycle.lifecycleScope.launch { | ||
| 94 | viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 95 | option.details.collect { updateOptionDetails(it) } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | binding.optionDetail.postDelayed( | ||
| 99 | { | ||
| 100 | binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
| 101 | binding.optionDetail.isSelected = true | ||
| 102 | }, | ||
| 103 | 3000 | ||
| 104 | ) | ||
| 105 | } | ||
| 106 | |||
| 107 | private fun updateOptionDetails(detailString: String) { | ||
| 108 | if (detailString.isNotEmpty()) { | ||
| 109 | binding.optionDetail.text = detailString | ||
| 110 | binding.optionDetail.visibility = View.VISIBLE | ||
| 111 | } | ||
| 82 | } | 112 | } |
| 83 | } | 113 | } |
| 84 | } | 114 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt index 7006651d0..bc6ff1364 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt | |||
| @@ -49,6 +49,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List | |||
| 49 | val context = YuzuApplication.appContext | 49 | val context = YuzuApplication.appContext |
| 50 | binding.textSettingName.text = context.getString(license.titleId) | 50 | binding.textSettingName.text = context.getString(license.titleId) |
| 51 | binding.textSettingDescription.text = context.getString(license.descriptionId) | 51 | binding.textSettingDescription.text = context.getString(license.descriptionId) |
| 52 | binding.textSettingValue.visibility = View.GONE | ||
| 52 | } | 53 | } |
| 53 | } | 54 | } |
| 54 | } | 55 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt index 481ddd5a5..6b46d359e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt | |||
| @@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.adapters | |||
| 5 | 5 | ||
| 6 | import android.text.Html | 6 | import android.text.Html |
| 7 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
| 8 | import android.view.View | ||
| 8 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 9 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.core.content.res.ResourcesCompat | 11 | import androidx.core.content.res.ResourcesCompat |
| 12 | import androidx.lifecycle.ViewModelProvider | ||
| 11 | import androidx.recyclerview.widget.RecyclerView | 13 | import androidx.recyclerview.widget.RecyclerView |
| 12 | import com.google.android.material.button.MaterialButton | 14 | import com.google.android.material.button.MaterialButton |
| 13 | import org.yuzu.yuzu_emu.databinding.PageSetupBinding | 15 | import org.yuzu.yuzu_emu.databinding.PageSetupBinding |
| 16 | import org.yuzu.yuzu_emu.model.HomeViewModel | ||
| 17 | import org.yuzu.yuzu_emu.model.SetupCallback | ||
| 14 | import org.yuzu.yuzu_emu.model.SetupPage | 18 | import org.yuzu.yuzu_emu.model.SetupPage |
| 19 | import org.yuzu.yuzu_emu.model.StepState | ||
| 20 | import org.yuzu.yuzu_emu.utils.ViewUtils | ||
| 15 | 21 | ||
| 16 | class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : | 22 | class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : |
| 17 | RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { | 23 | RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { |
| @@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 26 | holder.bind(pages[position]) | 32 | holder.bind(pages[position]) |
| 27 | 33 | ||
| 28 | inner class SetupPageViewHolder(val binding: PageSetupBinding) : | 34 | inner class SetupPageViewHolder(val binding: PageSetupBinding) : |
| 29 | RecyclerView.ViewHolder(binding.root) { | 35 | RecyclerView.ViewHolder(binding.root), SetupCallback { |
| 30 | lateinit var page: SetupPage | 36 | lateinit var page: SetupPage |
| 31 | 37 | ||
| 32 | init { | 38 | init { |
| @@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 35 | 41 | ||
| 36 | fun bind(page: SetupPage) { | 42 | fun bind(page: SetupPage) { |
| 37 | this.page = page | 43 | this.page = page |
| 44 | |||
| 45 | if (page.stepCompleted.invoke() == StepState.COMPLETE) { | ||
| 46 | binding.buttonAction.visibility = View.INVISIBLE | ||
| 47 | binding.textConfirmation.visibility = View.VISIBLE | ||
| 48 | } | ||
| 49 | |||
| 38 | binding.icon.setImageDrawable( | 50 | binding.icon.setImageDrawable( |
| 39 | ResourcesCompat.getDrawable( | 51 | ResourcesCompat.getDrawable( |
| 40 | activity.resources, | 52 | activity.resources, |
| @@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 62 | MaterialButton.ICON_GRAVITY_END | 74 | MaterialButton.ICON_GRAVITY_END |
| 63 | } | 75 | } |
| 64 | setOnClickListener { | 76 | setOnClickListener { |
| 65 | page.buttonAction.invoke() | 77 | page.buttonAction.invoke(this@SetupPageViewHolder) |
| 66 | } | 78 | } |
| 67 | } | 79 | } |
| 68 | } | 80 | } |
| 81 | |||
| 82 | override fun onStepCompleted() { | ||
| 83 | ViewUtils.hideView(binding.buttonAction, 200) | ||
| 84 | ViewUtils.showView(binding.textConfirmation, 200) | ||
| 85 | ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true) | ||
| 86 | } | ||
| 69 | } | 87 | } |
| 70 | } | 88 | } |
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 a18efef19..6f4b5b13f 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 | |||
| @@ -4,43 +4,43 @@ | |||
| 4 | package org.yuzu.yuzu_emu.disk_shader_cache | 4 | package org.yuzu.yuzu_emu.disk_shader_cache |
| 5 | 5 | ||
| 6 | import androidx.annotation.Keep | 6 | import androidx.annotation.Keep |
| 7 | import androidx.lifecycle.ViewModelProvider | ||
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | 8 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.R | 9 | import org.yuzu.yuzu_emu.R |
| 9 | import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment | 10 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 11 | import org.yuzu.yuzu_emu.model.EmulationViewModel | ||
| 12 | import org.yuzu.yuzu_emu.utils.Log | ||
| 10 | 13 | ||
| 11 | @Keep | 14 | @Keep |
| 12 | object DiskShaderCacheProgress { | 15 | object DiskShaderCacheProgress { |
| 13 | val finishLock = Object() | 16 | private lateinit var emulationViewModel: EmulationViewModel |
| 14 | private lateinit var fragment: ShaderProgressDialogFragment | ||
| 15 | 17 | ||
| 16 | private fun prepareDialog() { | 18 | private fun prepareViewModel() { |
| 17 | val emulationActivity = NativeLibrary.sEmulationActivity.get()!! | 19 | emulationViewModel = |
| 18 | emulationActivity.runOnUiThread { | 20 | ViewModelProvider( |
| 19 | fragment = ShaderProgressDialogFragment.newInstance( | 21 | NativeLibrary.sEmulationActivity.get() as EmulationActivity |
| 20 | emulationActivity.getString(R.string.loading), | 22 | )[EmulationViewModel::class.java] |
| 21 | emulationActivity.getString(R.string.preparing_shaders) | ||
| 22 | ) | ||
| 23 | fragment.show( | ||
| 24 | emulationActivity.supportFragmentManager, | ||
| 25 | ShaderProgressDialogFragment.TAG | ||
| 26 | ) | ||
| 27 | } | ||
| 28 | synchronized(finishLock) { finishLock.wait() } | ||
| 29 | } | 23 | } |
| 30 | 24 | ||
| 31 | @JvmStatic | 25 | @JvmStatic |
| 32 | fun loadProgress(stage: Int, progress: Int, max: Int) { | 26 | fun loadProgress(stage: Int, progress: Int, max: Int) { |
| 33 | val emulationActivity = NativeLibrary.sEmulationActivity.get() | 27 | val emulationActivity = NativeLibrary.sEmulationActivity.get() |
| 34 | ?: error("[DiskShaderCacheProgress] EmulationActivity not present") | 28 | if (emulationActivity == null) { |
| 35 | 29 | Log.error("[DiskShaderCacheProgress] EmulationActivity not present") | |
| 36 | when (LoadCallbackStage.values()[stage]) { | 30 | return |
| 37 | LoadCallbackStage.Prepare -> prepareDialog() | 31 | } |
| 38 | LoadCallbackStage.Build -> fragment.onUpdateProgress( | 32 | |
| 39 | emulationActivity.getString(R.string.building_shaders), | 33 | emulationActivity.runOnUiThread { |
| 40 | progress, | 34 | when (LoadCallbackStage.values()[stage]) { |
| 41 | max | 35 | LoadCallbackStage.Prepare -> prepareViewModel() |
| 42 | ) | 36 | LoadCallbackStage.Build -> emulationViewModel.updateProgress( |
| 43 | LoadCallbackStage.Complete -> fragment.dismiss() | 37 | emulationActivity.getString(R.string.building_shaders), |
| 38 | progress, | ||
| 39 | max | ||
| 40 | ) | ||
| 41 | |||
| 42 | LoadCallbackStage.Complete -> {} | ||
| 43 | } | ||
| 44 | } | 44 | } |
| 45 | } | 45 | } |
| 46 | 46 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt deleted file mode 100644 index bf6f0366d..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt +++ /dev/null | |||
| @@ -1,31 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.disk_shader_cache | ||
| 5 | |||
| 6 | import androidx.lifecycle.LiveData | ||
| 7 | import androidx.lifecycle.MutableLiveData | ||
| 8 | import androidx.lifecycle.ViewModel | ||
| 9 | |||
| 10 | class ShaderProgressViewModel : ViewModel() { | ||
| 11 | private val _progress = MutableLiveData(0) | ||
| 12 | val progress: LiveData<Int> get() = _progress | ||
| 13 | |||
| 14 | private val _max = MutableLiveData(0) | ||
| 15 | val max: LiveData<Int> get() = _max | ||
| 16 | |||
| 17 | private val _message = MutableLiveData("") | ||
| 18 | val message: LiveData<String> get() = _message | ||
| 19 | |||
| 20 | fun setProgress(progress: Int) { | ||
| 21 | _progress.postValue(progress) | ||
| 22 | } | ||
| 23 | |||
| 24 | fun setMax(max: Int) { | ||
| 25 | _max.postValue(max) | ||
| 26 | } | ||
| 27 | |||
| 28 | fun setMessage(msg: String) { | ||
| 29 | _message.postValue(msg) | ||
| 30 | } | ||
| 31 | } | ||
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 deleted file mode 100644 index 8a8e0a6e8..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt +++ /dev/null | |||
| @@ -1,103 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.disk_shader_cache.ui | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import androidx.appcompat.app.AlertDialog | ||
| 12 | import androidx.fragment.app.DialogFragment | ||
| 13 | import androidx.lifecycle.ViewModelProvider | ||
| 14 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 15 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | ||
| 16 | import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress | ||
| 17 | import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel | ||
| 18 | |||
| 19 | class ShaderProgressDialogFragment : DialogFragment() { | ||
| 20 | private var _binding: DialogProgressBarBinding? = null | ||
| 21 | private val binding get() = _binding!! | ||
| 22 | |||
| 23 | private lateinit var alertDialog: AlertDialog | ||
| 24 | |||
| 25 | private lateinit var shaderProgressViewModel: ShaderProgressViewModel | ||
| 26 | |||
| 27 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 28 | _binding = DialogProgressBarBinding.inflate(layoutInflater) | ||
| 29 | shaderProgressViewModel = | ||
| 30 | ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java] | ||
| 31 | |||
| 32 | val title = requireArguments().getString(TITLE) | ||
| 33 | val message = requireArguments().getString(MESSAGE) | ||
| 34 | |||
| 35 | isCancelable = false | ||
| 36 | alertDialog = MaterialAlertDialogBuilder(requireActivity()) | ||
| 37 | .setView(binding.root) | ||
| 38 | .setTitle(title) | ||
| 39 | .setMessage(message) | ||
| 40 | .create() | ||
| 41 | return alertDialog | ||
| 42 | } | ||
| 43 | |||
| 44 | override fun onCreateView( | ||
| 45 | inflater: LayoutInflater, | ||
| 46 | container: ViewGroup?, | ||
| 47 | savedInstanceState: Bundle? | ||
| 48 | ): View { | ||
| 49 | return binding.root | ||
| 50 | } | ||
| 51 | |||
| 52 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 53 | super.onViewCreated(view, savedInstanceState) | ||
| 54 | shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress -> | ||
| 55 | binding.progressBar.progress = progress | ||
| 56 | setUpdateText() | ||
| 57 | } | ||
| 58 | shaderProgressViewModel.max.observe(viewLifecycleOwner) { max -> | ||
| 59 | binding.progressBar.max = max | ||
| 60 | setUpdateText() | ||
| 61 | } | ||
| 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> | ||
| 63 | alertDialog.setMessage(msg) | ||
| 64 | } | ||
| 65 | synchronized(DiskShaderCacheProgress.finishLock) { | ||
| 66 | DiskShaderCacheProgress.finishLock.notifyAll() | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | override fun onDestroyView() { | ||
| 71 | super.onDestroyView() | ||
| 72 | _binding = null | ||
| 73 | } | ||
| 74 | |||
| 75 | fun onUpdateProgress(msg: String, progress: Int, max: Int) { | ||
| 76 | shaderProgressViewModel.setProgress(progress) | ||
| 77 | shaderProgressViewModel.setMax(max) | ||
| 78 | shaderProgressViewModel.setMessage(msg) | ||
| 79 | } | ||
| 80 | |||
| 81 | private fun setUpdateText() { | ||
| 82 | binding.progressText.text = String.format( | ||
| 83 | "%d/%d", | ||
| 84 | shaderProgressViewModel.progress.value, | ||
| 85 | shaderProgressViewModel.max.value | ||
| 86 | ) | ||
| 87 | } | ||
| 88 | |||
| 89 | companion object { | ||
| 90 | const val TAG = "ProgressDialogFragment" | ||
| 91 | const val TITLE = "title" | ||
| 92 | const val MESSAGE = "message" | ||
| 93 | |||
| 94 | fun newInstance(title: String, message: String): ShaderProgressDialogFragment { | ||
| 95 | val frag = ShaderProgressDialogFragment() | ||
| 96 | val args = Bundle() | ||
| 97 | args.putString(TITLE, title) | ||
| 98 | args.putString(MESSAGE, message) | ||
| 99 | frag.arguments = args | ||
| 100 | return frag | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt index a6e9833ee..aeda8d222 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractBooleanSetting : AbstractSetting { | 6 | interface AbstractBooleanSetting : AbstractSetting { |
| 7 | var boolean: Boolean | 7 | val boolean: Boolean |
| 8 | |||
| 9 | fun setBoolean(value: Boolean) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt index bd9233d62..606519ad8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import androidx.lifecycle.ViewModel | 6 | interface AbstractByteSetting : AbstractSetting { |
| 7 | val byte: Byte | ||
| 7 | 8 | ||
| 8 | class SettingsViewModel : ViewModel() { | 9 | fun setByte(value: Byte) |
| 9 | val settings = Settings() | ||
| 10 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt index 6fe4bc263..974925eed 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractFloatSetting : AbstractSetting { | 6 | interface AbstractFloatSetting : AbstractSetting { |
| 7 | var float: Float | 7 | val float: Float |
| 8 | |||
| 9 | fun setFloat(value: Float) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt index 892b7dcfe..89b285b10 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractIntSetting : AbstractSetting { | 6 | interface AbstractIntSetting : AbstractSetting { |
| 7 | var int: Int | 7 | val int: Int |
| 8 | |||
| 9 | fun setInt(value: Int) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt new file mode 100644 index 000000000..4873942db --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | interface AbstractLongSetting : AbstractSetting { | ||
| 7 | val long: Long | ||
| 8 | |||
| 9 | fun setLong(value: Long) | ||
| 10 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt index 258580209..8b6d29fe5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt | |||
| @@ -3,10 +3,22 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | interface AbstractSetting { | 8 | interface AbstractSetting { |
| 7 | val key: String? | 9 | val key: String |
| 8 | val section: String? | 10 | val category: Settings.Category |
| 9 | val isRuntimeEditable: Boolean | ||
| 10 | val valueAsString: String | ||
| 11 | val defaultValue: Any | 11 | val defaultValue: Any |
| 12 | val androidDefault: Any? | ||
| 13 | get() = null | ||
| 14 | val valueAsString: String | ||
| 15 | get() = "" | ||
| 16 | |||
| 17 | val isRuntimeModifiable: Boolean | ||
| 18 | get() = NativeConfig.getIsRuntimeModifiable(key) | ||
| 19 | |||
| 20 | val pairedSettingKey: String | ||
| 21 | get() = NativeConfig.getPairedSettingKey(key) | ||
| 22 | |||
| 23 | fun reset() | ||
| 12 | } | 24 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt new file mode 100644 index 000000000..91407ccbb --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | interface AbstractShortSetting : AbstractSetting { | ||
| 7 | val short: Short | ||
| 8 | |||
| 9 | fun setShort(value: Short) | ||
| 10 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt index 0d02c5997..c8935cc48 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractStringSetting : AbstractSetting { | 6 | interface AbstractStringSetting : AbstractSetting { |
| 7 | var string: String | 7 | val string: String |
| 8 | |||
| 9 | fun setString(value: String) | ||
| 8 | } | 10 | } |
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 d41933766..8476ce867 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 | |||
| @@ -3,41 +3,38 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class BooleanSetting( | 8 | enum class BooleanSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category, |
| 9 | override val defaultValue: Boolean | 11 | override val androidDefault: Boolean? = null |
| 10 | ) : AbstractBooleanSetting { | 12 | ) : AbstractBooleanSetting { |
| 11 | CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), | 13 | AUDIO_MUTED("audio_muted", Settings.Category.Audio), |
| 12 | FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), | 14 | CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), |
| 13 | FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), | 15 | FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), |
| 14 | PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), | 16 | FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), |
| 15 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); | 17 | RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core), |
| 16 | 18 | USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false), | |
| 17 | override var boolean: Boolean = defaultValue | 19 | RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer), |
| 20 | RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer), | ||
| 21 | RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer), | ||
| 22 | RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false), | ||
| 23 | RENDERER_DEBUG("debug", Settings.Category.Renderer), | ||
| 24 | PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android), | ||
| 25 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System); | ||
| 26 | |||
| 27 | override val boolean: Boolean | ||
| 28 | get() = NativeConfig.getBoolean(key, false) | ||
| 29 | |||
| 30 | override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value) | ||
| 31 | |||
| 32 | override val defaultValue: Boolean by lazy { | ||
| 33 | androidDefault ?: NativeConfig.getBoolean(key, true) | ||
| 34 | } | ||
| 18 | 35 | ||
| 19 | override val valueAsString: String | 36 | override val valueAsString: String |
| 20 | get() = boolean.toString() | 37 | get() = if (boolean) "1" else "0" |
| 21 | 38 | ||
| 22 | override val isRuntimeEditable: Boolean | 39 | override fun reset() = NativeConfig.setBoolean(key, defaultValue) |
| 23 | get() { | ||
| 24 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 25 | if (setting == this) { | ||
| 26 | return false | ||
| 27 | } | ||
| 28 | } | ||
| 29 | return true | ||
| 30 | } | ||
| 31 | |||
| 32 | companion object { | ||
| 33 | private val NOT_RUNTIME_EDITABLE = listOf( | ||
| 34 | PICTURE_IN_PICTURE, | ||
| 35 | USE_CUSTOM_RTC | ||
| 36 | ) | ||
| 37 | |||
| 38 | fun from(key: String): BooleanSetting? = | ||
| 39 | BooleanSetting.values().firstOrNull { it.key == key } | ||
| 40 | |||
| 41 | fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue } | ||
| 42 | } | ||
| 43 | } | 40 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt new file mode 100644 index 000000000..6ec0a765e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 8 | enum class ByteSetting( | ||
| 9 | override val key: String, | ||
| 10 | override val category: Settings.Category | ||
| 11 | ) : AbstractByteSetting { | ||
| 12 | AUDIO_VOLUME("volume", Settings.Category.Audio); | ||
| 13 | |||
| 14 | override val byte: Byte | ||
| 15 | get() = NativeConfig.getByte(key, false) | ||
| 16 | |||
| 17 | override fun setByte(value: Byte) = NativeConfig.setByte(key, value) | ||
| 18 | |||
| 19 | override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) } | ||
| 20 | |||
| 21 | override val valueAsString: String | ||
| 22 | get() = byte.toString() | ||
| 23 | |||
| 24 | override fun reset() = NativeConfig.setByte(key, defaultValue) | ||
| 25 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt index e5545a916..0181d06f2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt | |||
| @@ -3,34 +3,24 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class FloatSetting( | 8 | enum class FloatSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category |
| 9 | override val defaultValue: Float | ||
| 10 | ) : AbstractFloatSetting { | 11 | ) : AbstractFloatSetting { |
| 11 | // No float settings currently exist | 12 | // No float settings currently exist |
| 12 | EMPTY_SETTING("", "", 0f); | 13 | EMPTY_SETTING("", Settings.Category.UiGeneral); |
| 13 | |||
| 14 | override var float: Float = defaultValue | ||
| 15 | 14 | ||
| 16 | override val valueAsString: String | 15 | override val float: Float |
| 17 | get() = float.toString() | 16 | get() = NativeConfig.getFloat(key, false) |
| 18 | 17 | ||
| 19 | override val isRuntimeEditable: Boolean | 18 | override fun setFloat(value: Float) = NativeConfig.setFloat(key, value) |
| 20 | get() { | ||
| 21 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 22 | if (setting == this) { | ||
| 23 | return false | ||
| 24 | } | ||
| 25 | } | ||
| 26 | return true | ||
| 27 | } | ||
| 28 | 19 | ||
| 29 | companion object { | 20 | override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) } |
| 30 | private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>() | ||
| 31 | 21 | ||
| 32 | fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } | 22 | override val valueAsString: String |
| 23 | get() = float.toString() | ||
| 33 | 24 | ||
| 34 | fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue } | 25 | override fun reset() = NativeConfig.setFloat(key, defaultValue) |
| 35 | } | ||
| 36 | } | 26 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 4427a7d9d..151362124 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt | |||
| @@ -3,139 +3,37 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class IntSetting( | 8 | enum class IntSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category, |
| 9 | override val defaultValue: Int | 11 | override val androidDefault: Int? = null |
| 10 | ) : AbstractIntSetting { | 12 | ) : AbstractIntSetting { |
| 11 | RENDERER_USE_SPEED_LIMIT( | 13 | CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu), |
| 12 | "use_speed_limit", | 14 | REGION_INDEX("region_index", Settings.Category.System), |
| 13 | Settings.SECTION_RENDERER, | 15 | LANGUAGE_INDEX("language_index", Settings.Category.System), |
| 14 | 1 | 16 | RENDERER_BACKEND("backend", Settings.Category.Renderer), |
| 15 | ), | 17 | RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0), |
| 16 | USE_DOCKED_MODE( | 18 | RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer), |
| 17 | "use_docked_mode", | 19 | RENDERER_VSYNC("use_vsync", Settings.Category.Renderer), |
| 18 | Settings.SECTION_SYSTEM, | 20 | RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer), |
| 19 | 0 | 21 | RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer), |
| 20 | ), | 22 | RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android), |
| 21 | RENDERER_USE_DISK_SHADER_CACHE( | 23 | RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer), |
| 22 | "use_disk_shader_cache", | 24 | AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio); |
| 23 | Settings.SECTION_RENDERER, | 25 | |
| 24 | 1 | 26 | override val int: Int |
| 25 | ), | 27 | get() = NativeConfig.getInt(key, false) |
| 26 | RENDERER_FORCE_MAX_CLOCK( | 28 | |
| 27 | "force_max_clock", | 29 | override fun setInt(value: Int) = NativeConfig.setInt(key, value) |
| 28 | Settings.SECTION_RENDERER, | 30 | |
| 29 | 0 | 31 | override val defaultValue: Int by lazy { |
| 30 | ), | 32 | androidDefault ?: NativeConfig.getInt(key, true) |
| 31 | RENDERER_ASYNCHRONOUS_SHADERS( | 33 | } |
| 32 | "use_asynchronous_shaders", | ||
| 33 | Settings.SECTION_RENDERER, | ||
| 34 | 0 | ||
| 35 | ), | ||
| 36 | RENDERER_REACTIVE_FLUSHING( | ||
| 37 | "use_reactive_flushing", | ||
| 38 | Settings.SECTION_RENDERER, | ||
| 39 | 0 | ||
| 40 | ), | ||
| 41 | RENDERER_DEBUG( | ||
| 42 | "debug", | ||
| 43 | Settings.SECTION_RENDERER, | ||
| 44 | 0 | ||
| 45 | ), | ||
| 46 | RENDERER_SPEED_LIMIT( | ||
| 47 | "speed_limit", | ||
| 48 | Settings.SECTION_RENDERER, | ||
| 49 | 100 | ||
| 50 | ), | ||
| 51 | CPU_ACCURACY( | ||
| 52 | "cpu_accuracy", | ||
| 53 | Settings.SECTION_CPU, | ||
| 54 | 0 | ||
| 55 | ), | ||
| 56 | REGION_INDEX( | ||
| 57 | "region_index", | ||
| 58 | Settings.SECTION_SYSTEM, | ||
| 59 | -1 | ||
| 60 | ), | ||
| 61 | LANGUAGE_INDEX( | ||
| 62 | "language_index", | ||
| 63 | Settings.SECTION_SYSTEM, | ||
| 64 | 1 | ||
| 65 | ), | ||
| 66 | RENDERER_BACKEND( | ||
| 67 | "backend", | ||
| 68 | Settings.SECTION_RENDERER, | ||
| 69 | 1 | ||
| 70 | ), | ||
| 71 | RENDERER_ACCURACY( | ||
| 72 | "gpu_accuracy", | ||
| 73 | Settings.SECTION_RENDERER, | ||
| 74 | 0 | ||
| 75 | ), | ||
| 76 | RENDERER_RESOLUTION( | ||
| 77 | "resolution_setup", | ||
| 78 | Settings.SECTION_RENDERER, | ||
| 79 | 2 | ||
| 80 | ), | ||
| 81 | RENDERER_VSYNC( | ||
| 82 | "use_vsync", | ||
| 83 | Settings.SECTION_RENDERER, | ||
| 84 | 0 | ||
| 85 | ), | ||
| 86 | RENDERER_SCALING_FILTER( | ||
| 87 | "scaling_filter", | ||
| 88 | Settings.SECTION_RENDERER, | ||
| 89 | 1 | ||
| 90 | ), | ||
| 91 | RENDERER_ANTI_ALIASING( | ||
| 92 | "anti_aliasing", | ||
| 93 | Settings.SECTION_RENDERER, | ||
| 94 | 0 | ||
| 95 | ), | ||
| 96 | RENDERER_SCREEN_LAYOUT( | ||
| 97 | "screen_layout", | ||
| 98 | Settings.SECTION_RENDERER, | ||
| 99 | Settings.LayoutOption_MobileLandscape | ||
| 100 | ), | ||
| 101 | RENDERER_ASPECT_RATIO( | ||
| 102 | "aspect_ratio", | ||
| 103 | Settings.SECTION_RENDERER, | ||
| 104 | 0 | ||
| 105 | ), | ||
| 106 | AUDIO_VOLUME( | ||
| 107 | "volume", | ||
| 108 | Settings.SECTION_AUDIO, | ||
| 109 | 100 | ||
| 110 | ); | ||
| 111 | |||
| 112 | override var int: Int = defaultValue | ||
| 113 | 34 | ||
| 114 | override val valueAsString: String | 35 | override val valueAsString: String |
| 115 | get() = int.toString() | 36 | get() = int.toString() |
| 116 | 37 | ||
| 117 | override val isRuntimeEditable: Boolean | 38 | override fun reset() = NativeConfig.setInt(key, defaultValue) |
| 118 | get() { | ||
| 119 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 120 | if (setting == this) { | ||
| 121 | return false | ||
| 122 | } | ||
| 123 | } | ||
| 124 | return true | ||
| 125 | } | ||
| 126 | |||
| 127 | companion object { | ||
| 128 | private val NOT_RUNTIME_EDITABLE = listOf( | ||
| 129 | RENDERER_USE_DISK_SHADER_CACHE, | ||
| 130 | RENDERER_ASYNCHRONOUS_SHADERS, | ||
| 131 | RENDERER_DEBUG, | ||
| 132 | RENDERER_BACKEND, | ||
| 133 | RENDERER_RESOLUTION, | ||
| 134 | RENDERER_VSYNC | ||
| 135 | ) | ||
| 136 | |||
| 137 | fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } | ||
| 138 | |||
| 139 | fun clear() = IntSetting.values().forEach { it.int = it.defaultValue } | ||
| 140 | } | ||
| 141 | } | 39 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt new file mode 100644 index 000000000..c526fc4cf --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 8 | enum class LongSetting( | ||
| 9 | override val key: String, | ||
| 10 | override val category: Settings.Category | ||
| 11 | ) : AbstractLongSetting { | ||
| 12 | CUSTOM_RTC("custom_rtc", Settings.Category.System); | ||
| 13 | |||
| 14 | override val long: Long | ||
| 15 | get() = NativeConfig.getLong(key, false) | ||
| 16 | |||
| 17 | override fun setLong(value: Long) = NativeConfig.setLong(key, value) | ||
| 18 | |||
| 19 | override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) } | ||
| 20 | |||
| 21 | override val valueAsString: String | ||
| 22 | get() = long.toString() | ||
| 23 | |||
| 24 | override fun reset() = NativeConfig.setLong(key, defaultValue) | ||
| 25 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt deleted file mode 100644 index 474f598a9..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt +++ /dev/null | |||
| @@ -1,37 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | /** | ||
| 7 | * A semantically-related group of Settings objects. These Settings are | ||
| 8 | * internally stored as a HashMap. | ||
| 9 | */ | ||
| 10 | class SettingSection(val name: String) { | ||
| 11 | val settings = HashMap<String, AbstractSetting>() | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Convenience method; inserts a value directly into the backing HashMap. | ||
| 15 | * | ||
| 16 | * @param setting The Setting to be inserted. | ||
| 17 | */ | ||
| 18 | fun putSetting(setting: AbstractSetting) { | ||
| 19 | settings[setting.key!!] = setting | ||
| 20 | } | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Convenience method; gets a value directly from the backing HashMap. | ||
| 24 | * | ||
| 25 | * @param key Used to retrieve the Setting. | ||
| 26 | * @return A Setting object (you should probably cast this before using) | ||
| 27 | */ | ||
| 28 | fun getSetting(key: String): AbstractSetting? { | ||
| 29 | return settings[key] | ||
| 30 | } | ||
| 31 | |||
| 32 | fun mergeSection(settingSection: SettingSection) { | ||
| 33 | for (setting in settingSection.settings.values) { | ||
| 34 | putSetting(setting) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
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 a6251bafd..08e2a973d 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,195 +4,162 @@ | |||
| 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 android.widget.Toast |
| 8 | import org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
| 9 | import org.yuzu.yuzu_emu.YuzuApplication | 9 | import org.yuzu.yuzu_emu.YuzuApplication |
| 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 10 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 12 | 11 | ||
| 13 | class Settings { | 12 | object Settings { |
| 14 | private var gameId: String? = null | 13 | private val context get() = YuzuApplication.appContext |
| 15 | 14 | ||
| 16 | var isLoaded = false | 15 | fun saveSettings(gameId: String = "") { |
| 17 | |||
| 18 | /** | ||
| 19 | * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null | ||
| 20 | * when getting a key not already in the map | ||
| 21 | */ | ||
| 22 | class SettingsSectionMap : HashMap<String, SettingSection?>() { | ||
| 23 | override operator fun get(key: String): SettingSection? { | ||
| 24 | if (!super.containsKey(key)) { | ||
| 25 | val section = SettingSection(key) | ||
| 26 | super.put(key, section) | ||
| 27 | return section | ||
| 28 | } | ||
| 29 | return super.get(key) | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | var sections: HashMap<String, SettingSection?> = SettingsSectionMap() | ||
| 34 | |||
| 35 | fun getSection(sectionName: String): SettingSection? { | ||
| 36 | return sections[sectionName] | ||
| 37 | } | ||
| 38 | |||
| 39 | val isEmpty: Boolean | ||
| 40 | get() = sections.isEmpty() | ||
| 41 | |||
| 42 | fun loadSettings(view: SettingsActivityView? = null) { | ||
| 43 | sections = SettingsSectionMap() | ||
| 44 | loadYuzuSettings(view) | ||
| 45 | if (!TextUtils.isEmpty(gameId)) { | ||
| 46 | loadCustomGameSettings(gameId!!, view) | ||
| 47 | } | ||
| 48 | isLoaded = true | ||
| 49 | } | ||
| 50 | |||
| 51 | private fun loadYuzuSettings(view: SettingsActivityView?) { | ||
| 52 | for ((fileName) in configFileSectionsMap) { | ||
| 53 | sections.putAll(SettingsFile.readFile(fileName, view)) | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) { | ||
| 58 | // Custom game settings | ||
| 59 | mergeSections(SettingsFile.readCustomGameSettings(gameId, view)) | ||
| 60 | } | ||
| 61 | |||
| 62 | private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) { | ||
| 63 | for ((key, updatedSection) in updatedSections) { | ||
| 64 | if (sections.containsKey(key)) { | ||
| 65 | val originalSection = sections[key] | ||
| 66 | originalSection!!.mergeSection(updatedSection!!) | ||
| 67 | } else { | ||
| 68 | sections[key] = updatedSection | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | fun loadSettings(gameId: String, view: SettingsActivityView) { | ||
| 74 | this.gameId = gameId | ||
| 75 | loadSettings(view) | ||
| 76 | } | ||
| 77 | |||
| 78 | fun saveSettings(view: SettingsActivityView) { | ||
| 79 | if (TextUtils.isEmpty(gameId)) { | 16 | if (TextUtils.isEmpty(gameId)) { |
| 80 | view.showToastMessage( | 17 | Toast.makeText( |
| 81 | YuzuApplication.appContext.getString(R.string.ini_saved), | 18 | context, |
| 82 | false | 19 | context.getString(R.string.ini_saved), |
| 83 | ) | 20 | Toast.LENGTH_SHORT |
| 84 | 21 | ).show() | |
| 85 | for ((fileName, sectionNames) in configFileSectionsMap) { | 22 | SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG) |
| 86 | val iniSections = TreeMap<String, SettingSection>() | ||
| 87 | for (section in sectionNames) { | ||
| 88 | iniSections[section] = sections[section]!! | ||
| 89 | } | ||
| 90 | |||
| 91 | SettingsFile.saveFile(fileName, iniSections, view) | ||
| 92 | } | ||
| 93 | } else { | 23 | } else { |
| 94 | // Custom game settings | 24 | // TODO: Save custom game settings |
| 95 | view.showToastMessage( | 25 | Toast.makeText( |
| 96 | YuzuApplication.appContext.getString(R.string.gameid_saved, gameId), | 26 | context, |
| 97 | false | 27 | context.getString(R.string.gameid_saved, gameId), |
| 98 | ) | 28 | Toast.LENGTH_SHORT |
| 99 | 29 | ).show() | |
| 100 | SettingsFile.saveCustomGameSettings(gameId, sections) | ||
| 101 | } | 30 | } |
| 102 | } | 31 | } |
| 103 | 32 | ||
| 104 | companion object { | 33 | enum class Category { |
| 105 | const val SECTION_GENERAL = "General" | 34 | Android, |
| 106 | const val SECTION_SYSTEM = "System" | 35 | Audio, |
| 107 | const val SECTION_RENDERER = "Renderer" | 36 | Core, |
| 108 | const val SECTION_AUDIO = "Audio" | 37 | Cpu, |
| 109 | const val SECTION_CPU = "Cpu" | 38 | CpuDebug, |
| 110 | const val SECTION_THEME = "Theme" | 39 | CpuUnsafe, |
| 111 | const val SECTION_DEBUG = "Debug" | 40 | Renderer, |
| 112 | 41 | RendererAdvanced, | |
| 113 | const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" | 42 | RendererDebug, |
| 114 | 43 | System, | |
| 115 | const val PREF_OVERLAY_VERSION = "OverlayVersion" | 44 | SystemAudio, |
| 116 | const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" | 45 | DataStorage, |
| 117 | const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" | 46 | Debugging, |
| 118 | const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" | 47 | DebuggingGraphics, |
| 119 | val overlayLayoutPrefs = listOf( | 48 | Miscellaneous, |
| 120 | PREF_LANDSCAPE_OVERLAY_VERSION, | 49 | Network, |
| 121 | PREF_PORTRAIT_OVERLAY_VERSION, | 50 | WebService, |
| 122 | PREF_FOLDABLE_OVERLAY_VERSION | 51 | AddOns, |
| 123 | ) | 52 | Controls, |
| 124 | 53 | Ui, | |
| 125 | const val PREF_CONTROL_SCALE = "controlScale" | 54 | UiGeneral, |
| 126 | const val PREF_CONTROL_OPACITY = "controlOpacity" | 55 | UiLayout, |
| 127 | const val PREF_TOUCH_ENABLED = "isTouchEnabled" | 56 | UiGameList, |
| 128 | const val PREF_BUTTON_A = "buttonToggle0" | 57 | Screenshots, |
| 129 | const val PREF_BUTTON_B = "buttonToggle1" | 58 | Shortcuts, |
| 130 | const val PREF_BUTTON_X = "buttonToggle2" | 59 | Multiplayer, |
| 131 | const val PREF_BUTTON_Y = "buttonToggle3" | 60 | Services, |
| 132 | const val PREF_BUTTON_L = "buttonToggle4" | 61 | Paths, |
| 133 | const val PREF_BUTTON_R = "buttonToggle5" | 62 | MaxEnum |
| 134 | const val PREF_BUTTON_ZL = "buttonToggle6" | 63 | } |
| 135 | const val PREF_BUTTON_ZR = "buttonToggle7" | ||
| 136 | const val PREF_BUTTON_PLUS = "buttonToggle8" | ||
| 137 | const val PREF_BUTTON_MINUS = "buttonToggle9" | ||
| 138 | const val PREF_BUTTON_DPAD = "buttonToggle10" | ||
| 139 | const val PREF_STICK_L = "buttonToggle11" | ||
| 140 | const val PREF_STICK_R = "buttonToggle12" | ||
| 141 | const val PREF_BUTTON_STICK_L = "buttonToggle13" | ||
| 142 | const val PREF_BUTTON_STICK_R = "buttonToggle14" | ||
| 143 | const val PREF_BUTTON_HOME = "buttonToggle15" | ||
| 144 | const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" | ||
| 145 | |||
| 146 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" | ||
| 147 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" | ||
| 148 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" | ||
| 149 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" | ||
| 150 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" | ||
| 151 | |||
| 152 | const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" | ||
| 153 | const val PREF_THEME = "Theme" | ||
| 154 | const val PREF_THEME_MODE = "ThemeMode" | ||
| 155 | const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" | ||
| 156 | |||
| 157 | private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() | ||
| 158 | |||
| 159 | val overlayPreferences = listOf( | ||
| 160 | PREF_OVERLAY_VERSION, | ||
| 161 | PREF_CONTROL_SCALE, | ||
| 162 | PREF_CONTROL_OPACITY, | ||
| 163 | PREF_TOUCH_ENABLED, | ||
| 164 | PREF_BUTTON_A, | ||
| 165 | PREF_BUTTON_B, | ||
| 166 | PREF_BUTTON_X, | ||
| 167 | PREF_BUTTON_Y, | ||
| 168 | PREF_BUTTON_L, | ||
| 169 | PREF_BUTTON_R, | ||
| 170 | PREF_BUTTON_ZL, | ||
| 171 | PREF_BUTTON_ZR, | ||
| 172 | PREF_BUTTON_PLUS, | ||
| 173 | PREF_BUTTON_MINUS, | ||
| 174 | PREF_BUTTON_DPAD, | ||
| 175 | PREF_STICK_L, | ||
| 176 | PREF_STICK_R, | ||
| 177 | PREF_BUTTON_HOME, | ||
| 178 | PREF_BUTTON_SCREENSHOT, | ||
| 179 | PREF_BUTTON_STICK_L, | ||
| 180 | PREF_BUTTON_STICK_R | ||
| 181 | ) | ||
| 182 | |||
| 183 | const val LayoutOption_Unspecified = 0 | ||
| 184 | const val LayoutOption_MobilePortrait = 4 | ||
| 185 | const val LayoutOption_MobileLandscape = 5 | ||
| 186 | 64 | ||
| 187 | init { | 65 | val settingsList = listOf<AbstractSetting>( |
| 188 | configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = | 66 | *BooleanSetting.values(), |
| 189 | listOf( | 67 | *ByteSetting.values(), |
| 190 | SECTION_GENERAL, | 68 | *ShortSetting.values(), |
| 191 | SECTION_SYSTEM, | 69 | *IntSetting.values(), |
| 192 | SECTION_RENDERER, | 70 | *FloatSetting.values(), |
| 193 | SECTION_AUDIO, | 71 | *LongSetting.values(), |
| 194 | SECTION_CPU | 72 | *StringSetting.values() |
| 195 | ) | 73 | ) |
| 196 | } | 74 | |
| 75 | const val SECTION_GENERAL = "General" | ||
| 76 | const val SECTION_SYSTEM = "System" | ||
| 77 | const val SECTION_RENDERER = "Renderer" | ||
| 78 | const val SECTION_AUDIO = "Audio" | ||
| 79 | const val SECTION_CPU = "Cpu" | ||
| 80 | const val SECTION_THEME = "Theme" | ||
| 81 | const val SECTION_DEBUG = "Debug" | ||
| 82 | |||
| 83 | enum class MenuTag(val titleId: Int) { | ||
| 84 | SECTION_ROOT(R.string.advanced_settings), | ||
| 85 | SECTION_GENERAL(R.string.preferences_general), | ||
| 86 | SECTION_SYSTEM(R.string.preferences_system), | ||
| 87 | SECTION_RENDERER(R.string.preferences_graphics), | ||
| 88 | SECTION_AUDIO(R.string.preferences_audio), | ||
| 89 | SECTION_CPU(R.string.cpu), | ||
| 90 | SECTION_THEME(R.string.preferences_theme), | ||
| 91 | SECTION_DEBUG(R.string.preferences_debug); | ||
| 197 | } | 92 | } |
| 93 | |||
| 94 | const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" | ||
| 95 | |||
| 96 | const val PREF_OVERLAY_VERSION = "OverlayVersion" | ||
| 97 | const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" | ||
| 98 | const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" | ||
| 99 | const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" | ||
| 100 | val overlayLayoutPrefs = listOf( | ||
| 101 | PREF_LANDSCAPE_OVERLAY_VERSION, | ||
| 102 | PREF_PORTRAIT_OVERLAY_VERSION, | ||
| 103 | PREF_FOLDABLE_OVERLAY_VERSION | ||
| 104 | ) | ||
| 105 | |||
| 106 | const val PREF_CONTROL_SCALE = "controlScale" | ||
| 107 | const val PREF_CONTROL_OPACITY = "controlOpacity" | ||
| 108 | const val PREF_TOUCH_ENABLED = "isTouchEnabled" | ||
| 109 | const val PREF_BUTTON_A = "buttonToggle0" | ||
| 110 | const val PREF_BUTTON_B = "buttonToggle1" | ||
| 111 | const val PREF_BUTTON_X = "buttonToggle2" | ||
| 112 | const val PREF_BUTTON_Y = "buttonToggle3" | ||
| 113 | const val PREF_BUTTON_L = "buttonToggle4" | ||
| 114 | const val PREF_BUTTON_R = "buttonToggle5" | ||
| 115 | const val PREF_BUTTON_ZL = "buttonToggle6" | ||
| 116 | const val PREF_BUTTON_ZR = "buttonToggle7" | ||
| 117 | const val PREF_BUTTON_PLUS = "buttonToggle8" | ||
| 118 | const val PREF_BUTTON_MINUS = "buttonToggle9" | ||
| 119 | const val PREF_BUTTON_DPAD = "buttonToggle10" | ||
| 120 | const val PREF_STICK_L = "buttonToggle11" | ||
| 121 | const val PREF_STICK_R = "buttonToggle12" | ||
| 122 | const val PREF_BUTTON_STICK_L = "buttonToggle13" | ||
| 123 | const val PREF_BUTTON_STICK_R = "buttonToggle14" | ||
| 124 | const val PREF_BUTTON_HOME = "buttonToggle15" | ||
| 125 | const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" | ||
| 126 | |||
| 127 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" | ||
| 128 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" | ||
| 129 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" | ||
| 130 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" | ||
| 131 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" | ||
| 132 | |||
| 133 | const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" | ||
| 134 | const val PREF_THEME = "Theme" | ||
| 135 | const val PREF_THEME_MODE = "ThemeMode" | ||
| 136 | const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" | ||
| 137 | |||
| 138 | val overlayPreferences = listOf( | ||
| 139 | PREF_OVERLAY_VERSION, | ||
| 140 | PREF_CONTROL_SCALE, | ||
| 141 | PREF_CONTROL_OPACITY, | ||
| 142 | PREF_TOUCH_ENABLED, | ||
| 143 | PREF_BUTTON_A, | ||
| 144 | PREF_BUTTON_B, | ||
| 145 | PREF_BUTTON_X, | ||
| 146 | PREF_BUTTON_Y, | ||
| 147 | PREF_BUTTON_L, | ||
| 148 | PREF_BUTTON_R, | ||
| 149 | PREF_BUTTON_ZL, | ||
| 150 | PREF_BUTTON_ZR, | ||
| 151 | PREF_BUTTON_PLUS, | ||
| 152 | PREF_BUTTON_MINUS, | ||
| 153 | PREF_BUTTON_DPAD, | ||
| 154 | PREF_STICK_L, | ||
| 155 | PREF_STICK_R, | ||
| 156 | PREF_BUTTON_HOME, | ||
| 157 | PREF_BUTTON_SCREENSHOT, | ||
| 158 | PREF_BUTTON_STICK_L, | ||
| 159 | PREF_BUTTON_STICK_R | ||
| 160 | ) | ||
| 161 | |||
| 162 | const val LayoutOption_Unspecified = 0 | ||
| 163 | const val LayoutOption_MobilePortrait = 4 | ||
| 164 | const val LayoutOption_MobileLandscape = 5 | ||
| 198 | } | 165 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt new file mode 100644 index 000000000..c9a0c664c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 8 | enum class ShortSetting( | ||
| 9 | override val key: String, | ||
| 10 | override val category: Settings.Category | ||
| 11 | ) : AbstractShortSetting { | ||
| 12 | RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core); | ||
| 13 | |||
| 14 | override val short: Short | ||
| 15 | get() = NativeConfig.getShort(key, false) | ||
| 16 | |||
| 17 | override fun setShort(value: Short) = NativeConfig.setShort(key, value) | ||
| 18 | |||
| 19 | override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) } | ||
| 20 | |||
| 21 | override val valueAsString: String | ||
| 22 | get() = short.toString() | ||
| 23 | |||
| 24 | override fun reset() = NativeConfig.setShort(key, defaultValue) | ||
| 25 | } | ||
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 6621289fd..9bb3e66d4 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 | |||
| @@ -3,36 +3,24 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class StringSetting( | 8 | enum class StringSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category |
| 9 | override val defaultValue: String | ||
| 10 | ) : AbstractStringSetting { | 11 | ) : AbstractStringSetting { |
| 11 | AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), | 12 | // No string settings currently exist |
| 12 | CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); | 13 | EMPTY_SETTING("", Settings.Category.UiGeneral); |
| 14 | |||
| 15 | override val string: String | ||
| 16 | get() = NativeConfig.getString(key, false) | ||
| 17 | |||
| 18 | override fun setString(value: String) = NativeConfig.setString(key, value) | ||
| 13 | 19 | ||
| 14 | override var string: String = defaultValue | 20 | override val defaultValue: String by lazy { NativeConfig.getString(key, true) } |
| 15 | 21 | ||
| 16 | override val valueAsString: String | 22 | override val valueAsString: String |
| 17 | get() = string | 23 | get() = string |
| 18 | 24 | ||
| 19 | override val isRuntimeEditable: Boolean | 25 | override fun reset() = NativeConfig.setString(key, defaultValue) |
| 20 | get() { | ||
| 21 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 22 | if (setting == this) { | ||
| 23 | return false | ||
| 24 | } | ||
| 25 | } | ||
| 26 | return true | ||
| 27 | } | ||
| 28 | |||
| 29 | companion object { | ||
| 30 | private val NOT_RUNTIME_EDITABLE = listOf( | ||
| 31 | CUSTOM_RTC | ||
| 32 | ) | ||
| 33 | |||
| 34 | fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key } | ||
| 35 | |||
| 36 | fun clear() = StringSetting.values().forEach { it.string = it.defaultValue } | ||
| 37 | } | ||
| 38 | } | 26 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt index bc0bf7788..8bc164197 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt | |||
| @@ -3,29 +3,16 @@ | |||
| 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 | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | ||
| 8 | 7 | ||
| 9 | class DateTimeSetting( | 8 | class DateTimeSetting( |
| 10 | setting: AbstractSetting?, | 9 | private val longSetting: AbstractLongSetting, |
| 11 | titleId: Int, | 10 | titleId: Int, |
| 12 | descriptionId: Int, | 11 | descriptionId: Int |
| 13 | val key: String? = null, | 12 | ) : SettingsItem(longSetting, titleId, descriptionId) { |
| 14 | private val defaultValue: String? = null | ||
| 15 | ) : SettingsItem(setting, titleId, descriptionId) { | ||
| 16 | override val type = TYPE_DATETIME_SETTING | 13 | override val type = TYPE_DATETIME_SETTING |
| 17 | 14 | ||
| 18 | val value: String | 15 | var value: Long |
| 19 | get() = if (setting != null) { | 16 | get() = longSetting.long |
| 20 | val setting = setting as AbstractStringSetting | 17 | set(value) = (setting as AbstractLongSetting).setLong(value) |
| 21 | setting.string | ||
| 22 | } else { | ||
| 23 | defaultValue!! | ||
| 24 | } | ||
| 25 | |||
| 26 | fun setSelectedValue(datetime: String): AbstractStringSetting { | ||
| 27 | val stringSetting = setting as AbstractStringSetting | ||
| 28 | stringSetting.string = datetime | ||
| 29 | return stringSetting | ||
| 30 | } | ||
| 31 | } | 18 | } |
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 a67001311..d31ce1c31 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 | |||
| @@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view | |||
| 5 | 5 | ||
| 6 | class HeaderSetting( | 6 | class HeaderSetting( |
| 7 | titleId: Int | 7 | titleId: Int |
| 8 | ) : SettingsItem(null, titleId, 0) { | 8 | ) : SettingsItem(emptySetting, titleId, 0) { |
| 9 | override val type = TYPE_HEADER | 9 | override val type = TYPE_HEADER |
| 10 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt index caaab50d8..522cc49df 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt | |||
| @@ -8,6 +8,6 @@ class RunnableSetting( | |||
| 8 | descriptionId: Int, | 8 | descriptionId: Int, |
| 9 | val isRuntimeRunnable: Boolean, | 9 | val isRuntimeRunnable: Boolean, |
| 10 | val runnable: () -> Unit | 10 | val runnable: () -> Unit |
| 11 | ) : SettingsItem(null, titleId, descriptionId) { | 11 | ) : SettingsItem(emptySetting, titleId, descriptionId) { |
| 12 | override val type = TYPE_RUNNABLE | 12 | override val type = TYPE_RUNNABLE |
| 13 | } | 13 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 07520849e..b3b3fc209 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt | |||
| @@ -4,7 +4,15 @@ | |||
| 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.NativeLibrary | 6 | import org.yuzu.yuzu_emu.NativeLibrary |
| 7 | import org.yuzu.yuzu_emu.R | ||
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | ||
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.model.ByteSetting | ||
| 12 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 13 | import org.yuzu.yuzu_emu.features.settings.model.LongSetting | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.model.ShortSetting | ||
| 8 | 16 | ||
| 9 | /** | 17 | /** |
| 10 | * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. | 18 | * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. |
| @@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | |||
| 14 | * file.) | 22 | * file.) |
| 15 | */ | 23 | */ |
| 16 | abstract class SettingsItem( | 24 | abstract class SettingsItem( |
| 17 | var setting: AbstractSetting?, | 25 | val setting: AbstractSetting, |
| 18 | val nameId: Int, | 26 | val nameId: Int, |
| 19 | val descriptionId: Int | 27 | val descriptionId: Int |
| 20 | ) { | 28 | ) { |
| @@ -23,7 +31,7 @@ abstract class SettingsItem( | |||
| 23 | val isEditable: Boolean | 31 | val isEditable: Boolean |
| 24 | get() { | 32 | get() { |
| 25 | if (!NativeLibrary.isRunning()) return true | 33 | if (!NativeLibrary.isRunning()) return true |
| 26 | return setting?.isRuntimeEditable ?: false | 34 | return setting.isRuntimeModifiable |
| 27 | } | 35 | } |
| 28 | 36 | ||
| 29 | companion object { | 37 | companion object { |
| @@ -35,5 +43,240 @@ abstract class SettingsItem( | |||
| 35 | const val TYPE_STRING_SINGLE_CHOICE = 5 | 43 | const val TYPE_STRING_SINGLE_CHOICE = 5 |
| 36 | const val TYPE_DATETIME_SETTING = 6 | 44 | const val TYPE_DATETIME_SETTING = 6 |
| 37 | const val TYPE_RUNNABLE = 7 | 45 | const val TYPE_RUNNABLE = 7 |
| 46 | |||
| 47 | const val FASTMEM_COMBINED = "fastmem_combined" | ||
| 48 | |||
| 49 | val emptySetting = object : AbstractSetting { | ||
| 50 | override val key: String = "" | ||
| 51 | override val category: Settings.Category = Settings.Category.Ui | ||
| 52 | override val defaultValue: Any = false | ||
| 53 | override fun reset() {} | ||
| 54 | } | ||
| 55 | |||
| 56 | // Extension for putting SettingsItems into a hashmap without repeating yourself | ||
| 57 | fun HashMap<String, SettingsItem>.put(item: SettingsItem) { | ||
| 58 | put(item.setting.key, item) | ||
| 59 | } | ||
| 60 | |||
| 61 | // List of all general | ||
| 62 | val settingsItems = HashMap<String, SettingsItem>().apply { | ||
| 63 | put( | ||
| 64 | SwitchSetting( | ||
| 65 | BooleanSetting.RENDERER_USE_SPEED_LIMIT, | ||
| 66 | R.string.frame_limit_enable, | ||
| 67 | R.string.frame_limit_enable_description | ||
| 68 | ) | ||
| 69 | ) | ||
| 70 | put( | ||
| 71 | SliderSetting( | ||
| 72 | ShortSetting.RENDERER_SPEED_LIMIT, | ||
| 73 | R.string.frame_limit_slider, | ||
| 74 | R.string.frame_limit_slider_description, | ||
| 75 | 1, | ||
| 76 | 200, | ||
| 77 | "%" | ||
| 78 | ) | ||
| 79 | ) | ||
| 80 | put( | ||
| 81 | SingleChoiceSetting( | ||
| 82 | IntSetting.CPU_ACCURACY, | ||
| 83 | R.string.cpu_accuracy, | ||
| 84 | 0, | ||
| 85 | R.array.cpuAccuracyNames, | ||
| 86 | R.array.cpuAccuracyValues | ||
| 87 | ) | ||
| 88 | ) | ||
| 89 | put( | ||
| 90 | SwitchSetting( | ||
| 91 | BooleanSetting.PICTURE_IN_PICTURE, | ||
| 92 | R.string.picture_in_picture, | ||
| 93 | R.string.picture_in_picture_description | ||
| 94 | ) | ||
| 95 | ) | ||
| 96 | put( | ||
| 97 | SwitchSetting( | ||
| 98 | BooleanSetting.USE_DOCKED_MODE, | ||
| 99 | R.string.use_docked_mode, | ||
| 100 | R.string.use_docked_mode_description | ||
| 101 | ) | ||
| 102 | ) | ||
| 103 | put( | ||
| 104 | SingleChoiceSetting( | ||
| 105 | IntSetting.REGION_INDEX, | ||
| 106 | R.string.emulated_region, | ||
| 107 | 0, | ||
| 108 | R.array.regionNames, | ||
| 109 | R.array.regionValues | ||
| 110 | ) | ||
| 111 | ) | ||
| 112 | put( | ||
| 113 | SingleChoiceSetting( | ||
| 114 | IntSetting.LANGUAGE_INDEX, | ||
| 115 | R.string.emulated_language, | ||
| 116 | 0, | ||
| 117 | R.array.languageNames, | ||
| 118 | R.array.languageValues | ||
| 119 | ) | ||
| 120 | ) | ||
| 121 | put( | ||
| 122 | SwitchSetting( | ||
| 123 | BooleanSetting.USE_CUSTOM_RTC, | ||
| 124 | R.string.use_custom_rtc, | ||
| 125 | R.string.use_custom_rtc_description | ||
| 126 | ) | ||
| 127 | ) | ||
| 128 | put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0)) | ||
| 129 | put( | ||
| 130 | SingleChoiceSetting( | ||
| 131 | IntSetting.RENDERER_ACCURACY, | ||
| 132 | R.string.renderer_accuracy, | ||
| 133 | 0, | ||
| 134 | R.array.rendererAccuracyNames, | ||
| 135 | R.array.rendererAccuracyValues | ||
| 136 | ) | ||
| 137 | ) | ||
| 138 | put( | ||
| 139 | SingleChoiceSetting( | ||
| 140 | IntSetting.RENDERER_RESOLUTION, | ||
| 141 | R.string.renderer_resolution, | ||
| 142 | 0, | ||
| 143 | R.array.rendererResolutionNames, | ||
| 144 | R.array.rendererResolutionValues | ||
| 145 | ) | ||
| 146 | ) | ||
| 147 | put( | ||
| 148 | SingleChoiceSetting( | ||
| 149 | IntSetting.RENDERER_VSYNC, | ||
| 150 | R.string.renderer_vsync, | ||
| 151 | 0, | ||
| 152 | R.array.rendererVSyncNames, | ||
| 153 | R.array.rendererVSyncValues | ||
| 154 | ) | ||
| 155 | ) | ||
| 156 | put( | ||
| 157 | SingleChoiceSetting( | ||
| 158 | IntSetting.RENDERER_SCALING_FILTER, | ||
| 159 | R.string.renderer_scaling_filter, | ||
| 160 | 0, | ||
| 161 | R.array.rendererScalingFilterNames, | ||
| 162 | R.array.rendererScalingFilterValues | ||
| 163 | ) | ||
| 164 | ) | ||
| 165 | put( | ||
| 166 | SingleChoiceSetting( | ||
| 167 | IntSetting.RENDERER_ANTI_ALIASING, | ||
| 168 | R.string.renderer_anti_aliasing, | ||
| 169 | 0, | ||
| 170 | R.array.rendererAntiAliasingNames, | ||
| 171 | R.array.rendererAntiAliasingValues | ||
| 172 | ) | ||
| 173 | ) | ||
| 174 | put( | ||
| 175 | SingleChoiceSetting( | ||
| 176 | IntSetting.RENDERER_SCREEN_LAYOUT, | ||
| 177 | R.string.renderer_screen_layout, | ||
| 178 | 0, | ||
| 179 | R.array.rendererScreenLayoutNames, | ||
| 180 | R.array.rendererScreenLayoutValues | ||
| 181 | ) | ||
| 182 | ) | ||
| 183 | put( | ||
| 184 | SingleChoiceSetting( | ||
| 185 | IntSetting.RENDERER_ASPECT_RATIO, | ||
| 186 | R.string.renderer_aspect_ratio, | ||
| 187 | 0, | ||
| 188 | R.array.rendererAspectRatioNames, | ||
| 189 | R.array.rendererAspectRatioValues | ||
| 190 | ) | ||
| 191 | ) | ||
| 192 | put( | ||
| 193 | SwitchSetting( | ||
| 194 | BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, | ||
| 195 | R.string.use_disk_shader_cache, | ||
| 196 | R.string.use_disk_shader_cache_description | ||
| 197 | ) | ||
| 198 | ) | ||
| 199 | put( | ||
| 200 | SwitchSetting( | ||
| 201 | BooleanSetting.RENDERER_FORCE_MAX_CLOCK, | ||
| 202 | R.string.renderer_force_max_clock, | ||
| 203 | R.string.renderer_force_max_clock_description | ||
| 204 | ) | ||
| 205 | ) | ||
| 206 | put( | ||
| 207 | SwitchSetting( | ||
| 208 | BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, | ||
| 209 | R.string.renderer_asynchronous_shaders, | ||
| 210 | R.string.renderer_asynchronous_shaders_description | ||
| 211 | ) | ||
| 212 | ) | ||
| 213 | put( | ||
| 214 | SwitchSetting( | ||
| 215 | BooleanSetting.RENDERER_REACTIVE_FLUSHING, | ||
| 216 | R.string.renderer_reactive_flushing, | ||
| 217 | R.string.renderer_reactive_flushing_description | ||
| 218 | ) | ||
| 219 | ) | ||
| 220 | put( | ||
| 221 | SingleChoiceSetting( | ||
| 222 | IntSetting.AUDIO_OUTPUT_ENGINE, | ||
| 223 | R.string.audio_output_engine, | ||
| 224 | 0, | ||
| 225 | R.array.outputEngineEntries, | ||
| 226 | R.array.outputEngineValues | ||
| 227 | ) | ||
| 228 | ) | ||
| 229 | put( | ||
| 230 | SliderSetting( | ||
| 231 | ByteSetting.AUDIO_VOLUME, | ||
| 232 | R.string.audio_volume, | ||
| 233 | R.string.audio_volume_description, | ||
| 234 | 0, | ||
| 235 | 100, | ||
| 236 | "%" | ||
| 237 | ) | ||
| 238 | ) | ||
| 239 | put( | ||
| 240 | SingleChoiceSetting( | ||
| 241 | IntSetting.RENDERER_BACKEND, | ||
| 242 | R.string.renderer_api, | ||
| 243 | 0, | ||
| 244 | R.array.rendererApiNames, | ||
| 245 | R.array.rendererApiValues | ||
| 246 | ) | ||
| 247 | ) | ||
| 248 | put( | ||
| 249 | SwitchSetting( | ||
| 250 | BooleanSetting.RENDERER_DEBUG, | ||
| 251 | R.string.renderer_debug, | ||
| 252 | R.string.renderer_debug_description | ||
| 253 | ) | ||
| 254 | ) | ||
| 255 | put( | ||
| 256 | SwitchSetting( | ||
| 257 | BooleanSetting.CPU_DEBUG_MODE, | ||
| 258 | R.string.cpu_debug_mode, | ||
| 259 | R.string.cpu_debug_mode_description | ||
| 260 | ) | ||
| 261 | ) | ||
| 262 | |||
| 263 | val fastmem = object : AbstractBooleanSetting { | ||
| 264 | override val boolean: Boolean | ||
| 265 | get() = | ||
| 266 | BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean | ||
| 267 | |||
| 268 | override fun setBoolean(value: Boolean) { | ||
| 269 | BooleanSetting.FASTMEM.setBoolean(value) | ||
| 270 | BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value) | ||
| 271 | } | ||
| 272 | |||
| 273 | override val key: String = FASTMEM_COMBINED | ||
| 274 | override val category = Settings.Category.Cpu | ||
| 275 | override val isRuntimeModifiable: Boolean = false | ||
| 276 | override val defaultValue: Boolean = true | ||
| 277 | override fun reset() = setBoolean(defaultValue) | ||
| 278 | } | ||
| 279 | put(SwitchSetting(fastmem, R.string.fastmem, 0)) | ||
| 280 | } | ||
| 38 | } | 281 | } |
| 39 | } | 282 | } |
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 7306ec458..705527a73 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,36 +4,27 @@ | |||
| 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.AbstractSetting | ||
| 7 | 8 | ||
| 8 | class SingleChoiceSetting( | 9 | class SingleChoiceSetting( |
| 9 | setting: AbstractIntSetting?, | 10 | setting: AbstractSetting, |
| 10 | titleId: Int, | 11 | titleId: Int, |
| 11 | descriptionId: Int, | 12 | descriptionId: Int, |
| 12 | val choicesId: Int, | 13 | val choicesId: Int, |
| 13 | val valuesId: Int, | 14 | val valuesId: Int |
| 14 | val key: String? = null, | ||
| 15 | val defaultValue: Int? = null | ||
| 16 | ) : SettingsItem(setting, titleId, descriptionId) { | 15 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 17 | override val type = TYPE_SINGLE_CHOICE | 16 | override val type = TYPE_SINGLE_CHOICE |
| 18 | 17 | ||
| 19 | val selectedValue: Int | 18 | var selectedValue: Int |
| 20 | get() = if (setting != null) { | 19 | get() { |
| 21 | val setting = setting as AbstractIntSetting | 20 | return when (setting) { |
| 22 | setting.int | 21 | is AbstractIntSetting -> setting.int |
| 23 | } else { | 22 | else -> -1 |
| 24 | defaultValue!! | 23 | } |
| 24 | } | ||
| 25 | set(value) { | ||
| 26 | when (setting) { | ||
| 27 | is AbstractIntSetting -> setting.setInt(value) | ||
| 28 | } | ||
| 25 | } | 29 | } |
| 26 | |||
| 27 | /** | ||
| 28 | * Write a value to the backing int. If that int was previously null, | ||
| 29 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 30 | * | ||
| 31 | * @param selection New value of the int. | ||
| 32 | * @return the existing setting with the new value applied. | ||
| 33 | */ | ||
| 34 | fun setSelectedValue(selection: Int): AbstractIntSetting { | ||
| 35 | val intSetting = setting as AbstractIntSetting | ||
| 36 | intSetting.int = selection | ||
| 37 | return intSetting | ||
| 38 | } | ||
| 39 | } | 30 | } |
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 92d0167ae..c3b5df02c 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,60 +3,39 @@ | |||
| 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.AbstractByteSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 10 | import org.yuzu.yuzu_emu.utils.Log | 10 | import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting |
| 11 | import kotlin.math.roundToInt | ||
| 11 | 12 | ||
| 12 | class SliderSetting( | 13 | class SliderSetting( |
| 13 | setting: AbstractSetting?, | 14 | setting: AbstractSetting, |
| 14 | titleId: Int, | 15 | titleId: Int, |
| 15 | descriptionId: Int, | 16 | descriptionId: Int, |
| 16 | val min: Int, | 17 | val min: Int, |
| 17 | val max: Int, | 18 | val max: Int, |
| 18 | val units: String, | 19 | val units: String |
| 19 | val key: String? = null, | ||
| 20 | val defaultValue: Int? = null | ||
| 21 | ) : SettingsItem(setting, titleId, descriptionId) { | 20 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 22 | override val type = TYPE_SLIDER | 21 | override val type = TYPE_SLIDER |
| 23 | 22 | ||
| 24 | val selectedValue: Int | 23 | var selectedValue: Int |
| 25 | get() { | 24 | get() { |
| 26 | val setting = setting ?: return defaultValue!! | ||
| 27 | return when (setting) { | 25 | return when (setting) { |
| 26 | is AbstractByteSetting -> setting.byte.toInt() | ||
| 27 | is AbstractShortSetting -> setting.short.toInt() | ||
| 28 | is AbstractIntSetting -> setting.int | 28 | is AbstractIntSetting -> setting.int |
| 29 | is AbstractFloatSetting -> setting.float.roundToInt() | 29 | is AbstractFloatSetting -> setting.float.roundToInt() |
| 30 | else -> { | 30 | else -> -1 |
| 31 | Log.error("[SliderSetting] Error casting setting type.") | 31 | } |
| 32 | -1 | 32 | } |
| 33 | } | 33 | set(value) { |
| 34 | when (setting) { | ||
| 35 | is AbstractByteSetting -> setting.setByte(value.toByte()) | ||
| 36 | is AbstractShortSetting -> setting.setShort(value.toShort()) | ||
| 37 | is AbstractIntSetting -> setting.setInt(value) | ||
| 38 | is AbstractFloatSetting -> setting.setFloat(value.toFloat()) | ||
| 34 | } | 39 | } |
| 35 | } | 40 | } |
| 36 | |||
| 37 | /** | ||
| 38 | * Write a value to the backing int. If that int was previously null, | ||
| 39 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 40 | * | ||
| 41 | * @param selection New value of the int. | ||
| 42 | * @return the existing setting with the new value applied. | ||
| 43 | */ | ||
| 44 | fun setSelectedValue(selection: Int): AbstractIntSetting { | ||
| 45 | val intSetting = setting as AbstractIntSetting | ||
| 46 | intSetting.int = selection | ||
| 47 | return intSetting | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Write a value to the backing float. If that float was previously null, | ||
| 52 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 53 | * | ||
| 54 | * @param selection New value of the float. | ||
| 55 | * @return the existing setting with the new value applied. | ||
| 56 | */ | ||
| 57 | fun setSelectedValue(selection: Float): AbstractFloatSetting { | ||
| 58 | val floatSetting = setting as AbstractFloatSetting | ||
| 59 | floatSetting.float = selection | ||
| 60 | return floatSetting | ||
| 61 | } | ||
| 62 | } | 41 | } |
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 3b6731dcd..871dab4f3 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 | |||
| @@ -3,57 +3,31 @@ | |||
| 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 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting |
| 8 | 7 | ||
| 9 | class StringSingleChoiceSetting( | 8 | class StringSingleChoiceSetting( |
| 10 | setting: AbstractSetting?, | 9 | private val stringSetting: AbstractStringSetting, |
| 11 | titleId: Int, | 10 | titleId: Int, |
| 12 | descriptionId: Int, | 11 | descriptionId: Int, |
| 13 | val choices: Array<String>, | 12 | val choices: Array<String>, |
| 14 | val values: Array<String>?, | 13 | val values: Array<String> |
| 15 | val key: String? = null, | 14 | ) : SettingsItem(stringSetting, titleId, descriptionId) { |
| 16 | private val defaultValue: String? = null | ||
| 17 | ) : SettingsItem(setting, titleId, descriptionId) { | ||
| 18 | override val type = TYPE_STRING_SINGLE_CHOICE | 15 | override val type = TYPE_STRING_SINGLE_CHOICE |
| 19 | 16 | ||
| 20 | fun getValueAt(index: Int): String? { | 17 | fun getValueAt(index: Int): String = |
| 21 | if (values == null) return null | 18 | if (index >= 0 && index < values.size) values[index] else "" |
| 22 | return if (index >= 0 && index < values.size) { | 19 | |
| 23 | values[index] | 20 | var selectedValue: String |
| 24 | } else { | 21 | get() = stringSetting.string |
| 25 | "" | 22 | set(value) = stringSetting.setString(value) |
| 26 | } | ||
| 27 | } | ||
| 28 | 23 | ||
| 29 | val selectedValue: String | ||
| 30 | get() = if (setting != null) { | ||
| 31 | val setting = setting as AbstractStringSetting | ||
| 32 | setting.string | ||
| 33 | } else { | ||
| 34 | defaultValue!! | ||
| 35 | } | ||
| 36 | val selectValueIndex: Int | 24 | val selectValueIndex: Int |
| 37 | get() { | 25 | get() { |
| 38 | val selectedValue = selectedValue | 26 | for (i in values.indices) { |
| 39 | for (i in values!!.indices) { | ||
| 40 | if (values[i] == selectedValue) { | 27 | if (values[i] == selectedValue) { |
| 41 | return i | 28 | return i |
| 42 | } | 29 | } |
| 43 | } | 30 | } |
| 44 | return -1 | 31 | return -1 |
| 45 | } | 32 | } |
| 46 | |||
| 47 | /** | ||
| 48 | * Write a value to the backing int. If that int was previously null, | ||
| 49 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 50 | * | ||
| 51 | * @param selection New value of the int. | ||
| 52 | * @return the existing setting with the new value applied. | ||
| 53 | */ | ||
| 54 | fun setSelectedValue(selection: String): AbstractStringSetting { | ||
| 55 | val stringSetting = setting as AbstractStringSetting | ||
| 56 | stringSetting.string = selection | ||
| 57 | return stringSetting | ||
| 58 | } | ||
| 59 | } | 33 | } |
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 8a9d13a92..b343e527e 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,10 +3,12 @@ | |||
| 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.Settings | ||
| 7 | |||
| 6 | class SubmenuSetting( | 8 | class SubmenuSetting( |
| 7 | titleId: Int, | 9 | titleId: Int, |
| 8 | descriptionId: Int, | 10 | descriptionId: Int, |
| 9 | val menuKey: String | 11 | val menuKey: Settings.MenuTag |
| 10 | ) : SettingsItem(null, titleId, descriptionId) { | 12 | ) : SettingsItem(emptySetting, titleId, descriptionId) { |
| 11 | override val type = TYPE_SUBMENU | 13 | override val type = TYPE_SUBMENU |
| 12 | } | 14 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt index 90b198718..416967e64 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt | |||
| @@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | |||
| 10 | class SwitchSetting( | 10 | class SwitchSetting( |
| 11 | setting: AbstractSetting, | 11 | setting: AbstractSetting, |
| 12 | titleId: Int, | 12 | titleId: Int, |
| 13 | descriptionId: Int, | 13 | descriptionId: Int |
| 14 | val key: String? = null, | ||
| 15 | val defaultValue: Any? = null | ||
| 16 | ) : SettingsItem(setting, titleId, descriptionId) { | 14 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 17 | override val type = TYPE_SWITCH | 15 | override val type = TYPE_SWITCH |
| 18 | 16 | ||
| 19 | val isChecked: Boolean | 17 | var checked: Boolean |
| 20 | get() { | 18 | get() { |
| 21 | if (setting == null) { | 19 | return when (setting) { |
| 22 | return defaultValue as Boolean | 20 | is AbstractIntSetting -> setting.int == 1 |
| 21 | is AbstractBooleanSetting -> setting.boolean | ||
| 22 | else -> false | ||
| 23 | } | 23 | } |
| 24 | |||
| 25 | // Try integer setting | ||
| 26 | try { | ||
| 27 | val setting = setting as AbstractIntSetting | ||
| 28 | return setting.int == 1 | ||
| 29 | } catch (_: ClassCastException) { | ||
| 30 | } | ||
| 31 | |||
| 32 | // Try boolean setting | ||
| 33 | try { | ||
| 34 | val setting = setting as AbstractBooleanSetting | ||
| 35 | return setting.boolean | ||
| 36 | } catch (_: ClassCastException) { | ||
| 37 | } | ||
| 38 | return defaultValue as Boolean | ||
| 39 | } | 24 | } |
| 40 | 25 | set(value) { | |
| 41 | /** | 26 | when (setting) { |
| 42 | * Write a value to the backing boolean. If that boolean was previously null, | 27 | is AbstractIntSetting -> setting.setInt(if (value) 1 else 0) |
| 43 | * initializes a new one and returns it, so it can be added to the Hashmap. | 28 | is AbstractBooleanSetting -> setting.setBoolean(value) |
| 44 | * | 29 | } |
| 45 | * @param checked Pretty self explanatory. | ||
| 46 | * @return the existing setting with the new value applied. | ||
| 47 | */ | ||
| 48 | fun setChecked(checked: Boolean): AbstractSetting { | ||
| 49 | // Try integer setting | ||
| 50 | try { | ||
| 51 | val setting = setting as AbstractIntSetting | ||
| 52 | setting.int = if (checked) 1 else 0 | ||
| 53 | return setting | ||
| 54 | } catch (_: ClassCastException) { | ||
| 55 | } | 30 | } |
| 56 | |||
| 57 | // Try boolean setting | ||
| 58 | val setting = setting as AbstractBooleanSetting | ||
| 59 | setting.boolean = checked | ||
| 60 | return setting | ||
| 61 | } | ||
| 62 | } | 31 | } |
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 a5af5a7ae..4d2f2f604 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 | |||
| @@ -3,42 +3,39 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import android.content.Intent | ||
| 8 | import android.os.Bundle | 6 | import android.os.Bundle |
| 9 | import android.view.Menu | ||
| 10 | import android.view.View | 7 | import android.view.View |
| 11 | import android.view.ViewGroup.MarginLayoutParams | 8 | import android.view.ViewGroup.MarginLayoutParams |
| 12 | import android.widget.Toast | 9 | import android.widget.Toast |
| 13 | import androidx.activity.OnBackPressedCallback | 10 | import androidx.activity.OnBackPressedCallback |
| 14 | import androidx.activity.result.ActivityResultLauncher | ||
| 15 | import androidx.activity.viewModels | 11 | import androidx.activity.viewModels |
| 16 | import androidx.appcompat.app.AppCompatActivity | 12 | import androidx.appcompat.app.AppCompatActivity |
| 17 | import androidx.core.view.ViewCompat | 13 | import androidx.core.view.ViewCompat |
| 18 | import androidx.core.view.WindowCompat | 14 | import androidx.core.view.WindowCompat |
| 19 | import androidx.core.view.WindowInsetsCompat | 15 | import androidx.core.view.WindowInsetsCompat |
| 20 | import androidx.core.view.updatePadding | 16 | import androidx.lifecycle.Lifecycle |
| 17 | import androidx.lifecycle.lifecycleScope | ||
| 18 | import androidx.lifecycle.repeatOnLifecycle | ||
| 19 | import androidx.navigation.fragment.NavHostFragment | ||
| 20 | import androidx.navigation.navArgs | ||
| 21 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 22 | import kotlinx.coroutines.flow.collectLatest | ||
| 23 | import kotlinx.coroutines.launch | ||
| 22 | import java.io.IOException | 24 | import java.io.IOException |
| 23 | import org.yuzu.yuzu_emu.R | 25 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 26 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| 25 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 26 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 27 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 28 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 27 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 29 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | ||
| 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 31 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 28 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 29 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment | ||
| 30 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 32 | import org.yuzu.yuzu_emu.utils.* | 31 | import org.yuzu.yuzu_emu.utils.* |
| 33 | 32 | ||
| 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { | 33 | class SettingsActivity : AppCompatActivity() { |
| 35 | private val presenter = SettingsActivityPresenter(this) | ||
| 36 | |||
| 37 | private lateinit var binding: ActivitySettingsBinding | 34 | private lateinit var binding: ActivitySettingsBinding |
| 38 | 35 | ||
| 39 | private val settingsViewModel: SettingsViewModel by viewModels() | 36 | private val args by navArgs<SettingsActivityArgs>() |
| 40 | 37 | ||
| 41 | override val settings: Settings get() = settingsViewModel.settings | 38 | private val settingsViewModel: SettingsViewModel by viewModels() |
| 42 | 39 | ||
| 43 | override fun onCreate(savedInstanceState: Bundle?) { | 40 | override fun onCreate(savedInstanceState: Bundle?) { |
| 44 | ThemeHelper.setTheme(this) | 41 | ThemeHelper.setTheme(this) |
| @@ -48,16 +45,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 48 | binding = ActivitySettingsBinding.inflate(layoutInflater) | 45 | binding = ActivitySettingsBinding.inflate(layoutInflater) |
| 49 | setContentView(binding.root) | 46 | setContentView(binding.root) |
| 50 | 47 | ||
| 51 | WindowCompat.setDecorFitsSystemWindows(window, false) | 48 | settingsViewModel.game = args.game |
| 52 | 49 | ||
| 53 | val launcher = intent | 50 | val navHostFragment = |
| 54 | val gameID = launcher.getStringExtra(ARG_GAME_ID) | 51 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 55 | val menuTag = launcher.getStringExtra(ARG_MENU_TAG) | 52 | navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras) |
| 56 | presenter.onCreate(savedInstanceState, menuTag!!, gameID!!) | ||
| 57 | 53 | ||
| 58 | // Show "Back" button in the action bar for navigation | 54 | WindowCompat.setDecorFitsSystemWindows(window, false) |
| 59 | setSupportActionBar(binding.toolbarSettings) | 55 | |
| 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) | 56 | if (savedInstanceState != null) { |
| 57 | settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) | ||
| 58 | } | ||
| 61 | 59 | ||
| 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != | 60 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 63 | InsetsHelper.GESTURE_NAVIGATION | 61 | InsetsHelper.GESTURE_NAVIGATION |
| @@ -73,6 +71,42 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 73 | ) | 71 | ) |
| 74 | } | 72 | } |
| 75 | 73 | ||
| 74 | lifecycleScope.apply { | ||
| 75 | launch { | ||
| 76 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 77 | settingsViewModel.shouldRecreate.collectLatest { | ||
| 78 | if (it) { | ||
| 79 | settingsViewModel.setShouldRecreate(false) | ||
| 80 | recreate() | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | launch { | ||
| 86 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 87 | settingsViewModel.shouldNavigateBack.collectLatest { | ||
| 88 | if (it) { | ||
| 89 | settingsViewModel.setShouldNavigateBack(false) | ||
| 90 | navigateBack() | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | launch { | ||
| 96 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 97 | settingsViewModel.shouldShowResetSettingsDialog.collectLatest { | ||
| 98 | if (it) { | ||
| 99 | settingsViewModel.setShouldShowResetSettingsDialog(false) | ||
| 100 | ResetSettingsDialogFragment().show( | ||
| 101 | supportFragmentManager, | ||
| 102 | ResetSettingsDialogFragment.TAG | ||
| 103 | ) | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 76 | onBackPressedDispatcher.addCallback( | 110 | onBackPressedDispatcher.addCallback( |
| 77 | this, | 111 | this, |
| 78 | object : OnBackPressedCallback(true) { | 112 | object : OnBackPressedCallback(true) { |
| @@ -83,34 +117,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 83 | setInsets() | 117 | setInsets() |
| 84 | } | 118 | } |
| 85 | 119 | ||
| 86 | override fun onSupportNavigateUp(): Boolean { | 120 | fun navigateBack() { |
| 87 | navigateBack() | 121 | val navHostFragment = |
| 88 | return true | 122 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 89 | } | 123 | if (navHostFragment.childFragmentManager.backStackEntryCount > 0) { |
| 90 | 124 | navHostFragment.navController.popBackStack() | |
| 91 | private fun navigateBack() { | ||
| 92 | if (supportFragmentManager.backStackEntryCount > 0) { | ||
| 93 | supportFragmentManager.popBackStack() | ||
| 94 | } else { | 125 | } else { |
| 95 | finish() | 126 | finish() |
| 96 | } | 127 | } |
| 97 | } | 128 | } |
| 98 | 129 | ||
| 99 | override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||
| 100 | val inflater = menuInflater | ||
| 101 | inflater.inflate(R.menu.menu_settings, menu) | ||
| 102 | return true | ||
| 103 | } | ||
| 104 | |||
| 105 | override fun onSaveInstanceState(outState: Bundle) { | 130 | override fun onSaveInstanceState(outState: Bundle) { |
| 106 | // Critical: If super method is not called, rotations will be busted. | 131 | // Critical: If super method is not called, rotations will be busted. |
| 107 | super.onSaveInstanceState(outState) | 132 | super.onSaveInstanceState(outState) |
| 108 | presenter.saveState(outState) | 133 | outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave) |
| 109 | } | 134 | } |
| 110 | 135 | ||
| 111 | override fun onStart() { | 136 | override fun onStart() { |
| 112 | super.onStart() | 137 | super.onStart() |
| 113 | presenter.onStart() | 138 | // TODO: Load custom settings contextually |
| 139 | if (!DirectoryInitialization.areDirectoriesReady) { | ||
| 140 | DirectoryInitialization.start() | ||
| 141 | } | ||
| 114 | } | 142 | } |
| 115 | 143 | ||
| 116 | /** | 144 | /** |
| @@ -120,143 +148,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 120 | */ | 148 | */ |
| 121 | override fun onStop() { | 149 | override fun onStop() { |
| 122 | super.onStop() | 150 | super.onStop() |
| 123 | presenter.onStop(isFinishing) | 151 | if (isFinishing && settingsViewModel.shouldSave) { |
| 124 | } | 152 | Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") |
| 125 | 153 | Settings.saveSettings() | |
| 126 | override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) { | ||
| 127 | if (!addToStack && settingsFragment != null) { | ||
| 128 | return | ||
| 129 | } | 154 | } |
| 130 | |||
| 131 | val transaction = supportFragmentManager.beginTransaction() | ||
| 132 | if (addToStack) { | ||
| 133 | if (areSystemAnimationsEnabled()) { | ||
| 134 | transaction.setCustomAnimations( | ||
| 135 | R.anim.anim_settings_fragment_in, | ||
| 136 | R.anim.anim_settings_fragment_out, | ||
| 137 | 0, | ||
| 138 | R.anim.anim_pop_settings_fragment_out | ||
| 139 | ) | ||
| 140 | } | ||
| 141 | transaction.addToBackStack(null) | ||
| 142 | } | ||
| 143 | transaction.replace( | ||
| 144 | R.id.frame_content, | ||
| 145 | SettingsFragment.newInstance(menuTag, gameId), | ||
| 146 | FRAGMENT_TAG | ||
| 147 | ) | ||
| 148 | transaction.commit() | ||
| 149 | } | ||
| 150 | |||
| 151 | private fun areSystemAnimationsEnabled(): Boolean { | ||
| 152 | val duration = android.provider.Settings.Global.getFloat( | ||
| 153 | contentResolver, | ||
| 154 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, | ||
| 155 | 1f | ||
| 156 | ) | ||
| 157 | val transition = android.provider.Settings.Global.getFloat( | ||
| 158 | contentResolver, | ||
| 159 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, | ||
| 160 | 1f | ||
| 161 | ) | ||
| 162 | return duration != 0f && transition != 0f | ||
| 163 | } | 155 | } |
| 164 | 156 | ||
| 165 | override fun onSettingsFileLoaded() { | 157 | override fun onDestroy() { |
| 166 | val fragment: SettingsFragmentView? = settingsFragment | 158 | settingsViewModel.clear() |
| 167 | fragment?.loadSettingsList() | 159 | super.onDestroy() |
| 168 | } | ||
| 169 | |||
| 170 | override fun onSettingsFileNotFound() { | ||
| 171 | val fragment: SettingsFragmentView? = settingsFragment | ||
| 172 | fragment?.loadSettingsList() | ||
| 173 | } | ||
| 174 | |||
| 175 | override fun showToastMessage(message: String, is_long: Boolean) { | ||
| 176 | Toast.makeText( | ||
| 177 | this, | ||
| 178 | message, | ||
| 179 | if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT | ||
| 180 | ).show() | ||
| 181 | } | ||
| 182 | |||
| 183 | override fun onSettingChanged() { | ||
| 184 | presenter.onSettingChanged() | ||
| 185 | } | 160 | } |
| 186 | 161 | ||
| 187 | fun onSettingsReset() { | 162 | fun onSettingsReset() { |
| 188 | // Prevents saving to a non-existent settings file | 163 | // Prevents saving to a non-existent settings file |
| 189 | presenter.onSettingsReset() | 164 | settingsViewModel.shouldSave = false |
| 190 | |||
| 191 | // Reset the static memory representation of each setting | ||
| 192 | BooleanSetting.clear() | ||
| 193 | FloatSetting.clear() | ||
| 194 | IntSetting.clear() | ||
| 195 | StringSetting.clear() | ||
| 196 | 165 | ||
| 197 | // Delete settings file because the user may have changed values that do not exist in the UI | 166 | // Delete settings file because the user may have changed values that do not exist in the UI |
| 198 | val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) | 167 | val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) |
| 199 | if (!settingsFile.delete()) { | 168 | if (!settingsFile.delete()) { |
| 200 | throw IOException("Failed to delete $settingsFile") | 169 | throw IOException("Failed to delete $settingsFile") |
| 201 | } | 170 | } |
| 171 | Settings.settingsList.forEach { it.reset() } | ||
| 202 | 172 | ||
| 203 | showToastMessage(getString(R.string.settings_reset), true) | 173 | Toast.makeText( |
| 174 | applicationContext, | ||
| 175 | getString(R.string.settings_reset), | ||
| 176 | Toast.LENGTH_LONG | ||
| 177 | ).show() | ||
| 204 | finish() | 178 | finish() |
| 205 | } | 179 | } |
| 206 | 180 | ||
| 207 | fun setToolbarTitle(title: String) { | ||
| 208 | binding.toolbarSettingsLayout.title = title | ||
| 209 | } | ||
| 210 | |||
| 211 | private val settingsFragment: SettingsFragment? | ||
| 212 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? | ||
| 213 | |||
| 214 | private fun setInsets() { | 181 | private fun setInsets() { |
| 215 | ViewCompat.setOnApplyWindowInsetsListener( | 182 | ViewCompat.setOnApplyWindowInsetsListener( |
| 216 | binding.frameContent | 183 | binding.navigationBarShade |
| 217 | ) { view: View, windowInsets: WindowInsetsCompat -> | 184 | ) { view: View, windowInsets: WindowInsetsCompat -> |
| 218 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 185 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 219 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 220 | view.updatePadding( | ||
| 221 | left = barInsets.left + cutoutInsets.left, | ||
| 222 | right = barInsets.right + cutoutInsets.right | ||
| 223 | ) | ||
| 224 | 186 | ||
| 225 | val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams | 187 | val mlpShade = view.layoutParams as MarginLayoutParams |
| 226 | mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left | ||
| 227 | mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right | ||
| 228 | binding.appbarSettings.layoutParams = mlpAppBar | ||
| 229 | |||
| 230 | val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams | ||
| 231 | mlpShade.height = barInsets.bottom | 188 | mlpShade.height = barInsets.bottom |
| 232 | binding.navigationBarShade.layoutParams = mlpShade | 189 | view.layoutParams = mlpShade |
| 233 | 190 | ||
| 234 | windowInsets | 191 | windowInsets |
| 235 | } | 192 | } |
| 236 | } | 193 | } |
| 237 | 194 | ||
| 238 | companion object { | 195 | companion object { |
| 239 | private const val ARG_MENU_TAG = "menu_tag" | 196 | private const val KEY_SHOULD_SAVE = "should_save" |
| 240 | private const val ARG_GAME_ID = "game_id" | ||
| 241 | private const val FRAGMENT_TAG = "settings" | ||
| 242 | |||
| 243 | fun launch(context: Context, menuTag: String?, gameId: String?) { | ||
| 244 | val settings = Intent(context, SettingsActivity::class.java) | ||
| 245 | settings.putExtra(ARG_MENU_TAG, menuTag) | ||
| 246 | settings.putExtra(ARG_GAME_ID, gameId) | ||
| 247 | context.startActivity(settings) | ||
| 248 | } | ||
| 249 | |||
| 250 | fun launch( | ||
| 251 | context: Context, | ||
| 252 | launcher: ActivityResultLauncher<Intent>, | ||
| 253 | menuTag: String?, | ||
| 254 | gameId: String? | ||
| 255 | ) { | ||
| 256 | val settings = Intent(context, SettingsActivity::class.java) | ||
| 257 | settings.putExtra(ARG_MENU_TAG, menuTag) | ||
| 258 | settings.putExtra(ARG_GAME_ID, gameId) | ||
| 259 | launcher.launch(settings) | ||
| 260 | } | ||
| 261 | } | 197 | } |
| 262 | } | 198 | } |
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 deleted file mode 100644 index 93e677b21..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ /dev/null | |||
| @@ -1,90 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
| 5 | |||
| 6 | import android.content.Context | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.text.TextUtils | ||
| 9 | import java.io.File | ||
| 10 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 12 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | ||
| 13 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | ||
| 14 | import org.yuzu.yuzu_emu.utils.Log | ||
| 15 | |||
| 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { | ||
| 17 | val settings: Settings get() = activityView.settings | ||
| 18 | |||
| 19 | private var shouldSave = false | ||
| 20 | private lateinit var menuTag: String | ||
| 21 | private lateinit var gameId: String | ||
| 22 | |||
| 23 | fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) { | ||
| 24 | this.menuTag = menuTag | ||
| 25 | this.gameId = gameId | ||
| 26 | if (savedInstanceState != null) { | ||
| 27 | shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | fun onStart() { | ||
| 32 | prepareDirectoriesIfNeeded() | ||
| 33 | } | ||
| 34 | |||
| 35 | private fun loadSettingsUI() { | ||
| 36 | if (!settings.isLoaded) { | ||
| 37 | if (!TextUtils.isEmpty(gameId)) { | ||
| 38 | settings.loadSettings(gameId, activityView) | ||
| 39 | } else { | ||
| 40 | settings.loadSettings(activityView) | ||
| 41 | } | ||
| 42 | } | ||
| 43 | activityView.showSettingsFragment(menuTag, false, gameId) | ||
| 44 | activityView.onSettingsFileLoaded() | ||
| 45 | } | ||
| 46 | |||
| 47 | private fun prepareDirectoriesIfNeeded() { | ||
| 48 | val configFile = | ||
| 49 | File( | ||
| 50 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 51 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 52 | ) | ||
| 53 | if (!configFile.exists()) { | ||
| 54 | Log.error( | ||
| 55 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 56 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 57 | ) | ||
| 58 | Log.error("yuzu config file could not be found!") | ||
| 59 | } | ||
| 60 | |||
| 61 | if (!DirectoryInitialization.areDirectoriesReady) { | ||
| 62 | DirectoryInitialization.start(activityView as Context) | ||
| 63 | } | ||
| 64 | loadSettingsUI() | ||
| 65 | } | ||
| 66 | |||
| 67 | fun onStop(finishing: Boolean) { | ||
| 68 | if (finishing && shouldSave) { | ||
| 69 | Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") | ||
| 70 | settings.saveSettings(activityView) | ||
| 71 | } | ||
| 72 | NativeLibrary.reloadSettings() | ||
| 73 | } | ||
| 74 | |||
| 75 | fun onSettingChanged() { | ||
| 76 | shouldSave = true | ||
| 77 | } | ||
| 78 | |||
| 79 | fun onSettingsReset() { | ||
| 80 | shouldSave = false | ||
| 81 | } | ||
| 82 | |||
| 83 | fun saveState(outState: Bundle) { | ||
| 84 | outState.putBoolean(KEY_SHOULD_SAVE, shouldSave) | ||
| 85 | } | ||
| 86 | |||
| 87 | companion object { | ||
| 88 | private const val KEY_SHOULD_SAVE = "should_save" | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt deleted file mode 100644 index c186fc388..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt +++ /dev/null | |||
| @@ -1,57 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Abstraction for the Activity that manages SettingsFragments. | ||
| 10 | */ | ||
| 11 | interface SettingsActivityView { | ||
| 12 | /** | ||
| 13 | * Show a new SettingsFragment. | ||
| 14 | * | ||
| 15 | * @param menuTag Identifier for the settings group that should be displayed. | ||
| 16 | * @param addToStack Whether or not this fragment should replace a previous one. | ||
| 17 | */ | ||
| 18 | fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Called by a contained Fragment to get access to the Setting HashMap | ||
| 22 | * loaded from disk, so that each Fragment doesn't need to perform its own | ||
| 23 | * read operation. | ||
| 24 | * | ||
| 25 | * @return A HashMap of Settings. | ||
| 26 | */ | ||
| 27 | val settings: Settings | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Called when a load operation completes. | ||
| 31 | */ | ||
| 32 | fun onSettingsFileLoaded() | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Called when a load operation fails. | ||
| 36 | */ | ||
| 37 | fun onSettingsFileNotFound() | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Display a popup text message on screen. | ||
| 41 | * | ||
| 42 | * @param message The contents of the onscreen message. | ||
| 43 | * @param is_long Whether this should be a long Toast or short one. | ||
| 44 | */ | ||
| 45 | fun showToastMessage(message: String, is_long: Boolean) | ||
| 46 | |||
| 47 | /** | ||
| 48 | * End the activity. | ||
| 49 | */ | ||
| 50 | fun finish() | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Called by a containing Fragment to tell the Activity that a setting was changed; | ||
| 54 | * unless this has been called, the Activity will not save to disk. | ||
| 55 | */ | ||
| 56 | fun onSettingChanged() | ||
| 57 | } | ||
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 ce0b92c90..a7a029fc1 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 | |||
| @@ -4,51 +4,54 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.content.DialogInterface | ||
| 8 | import android.icu.util.Calendar | 7 | import android.icu.util.Calendar |
| 9 | import android.icu.util.TimeZone | 8 | import android.icu.util.TimeZone |
| 10 | import android.text.format.DateFormat | 9 | import android.text.format.DateFormat |
| 11 | import android.view.LayoutInflater | 10 | import android.view.LayoutInflater |
| 12 | import android.view.ViewGroup | 11 | import android.view.ViewGroup |
| 13 | import android.widget.TextView | 12 | import androidx.fragment.app.Fragment |
| 14 | import androidx.appcompat.app.AlertDialog | 13 | import androidx.lifecycle.Lifecycle |
| 15 | import androidx.appcompat.app.AppCompatActivity | 14 | import androidx.lifecycle.ViewModelProvider |
| 16 | import androidx.recyclerview.widget.RecyclerView | 15 | import androidx.lifecycle.lifecycleScope |
| 16 | import androidx.lifecycle.repeatOnLifecycle | ||
| 17 | import androidx.navigation.findNavController | ||
| 18 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 19 | import androidx.recyclerview.widget.DiffUtil | ||
| 20 | import androidx.recyclerview.widget.ListAdapter | ||
| 17 | import com.google.android.material.datepicker.MaterialDatePicker | 21 | import com.google.android.material.datepicker.MaterialDatePicker |
| 18 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 19 | import com.google.android.material.slider.Slider | ||
| 20 | import com.google.android.material.timepicker.MaterialTimePicker | 22 | import com.google.android.material.timepicker.MaterialTimePicker |
| 21 | import com.google.android.material.timepicker.TimeFormat | 23 | import com.google.android.material.timepicker.TimeFormat |
| 24 | import kotlinx.coroutines.launch | ||
| 22 | import org.yuzu.yuzu_emu.R | 25 | import org.yuzu.yuzu_emu.R |
| 23 | import org.yuzu.yuzu_emu.databinding.DialogSliderBinding | 26 | import org.yuzu.yuzu_emu.SettingsNavigationDirections |
| 24 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 27 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
| 25 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | 28 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding |
| 26 | import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding | 29 | import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding |
| 27 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | ||
| 28 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | ||
| 29 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | ||
| 30 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 31 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | ||
| 32 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 33 | import org.yuzu.yuzu_emu.features.settings.model.view.* | 30 | import org.yuzu.yuzu_emu.features.settings.model.view.* |
| 34 | import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* | 31 | import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* |
| 32 | import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment | ||
| 33 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 35 | 34 | ||
| 36 | class SettingsAdapter( | 35 | class SettingsAdapter( |
| 37 | private val fragmentView: SettingsFragmentView, | 36 | private val fragment: Fragment, |
| 38 | private val context: Context | 37 | private val context: Context |
| 39 | ) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener { | 38 | ) : ListAdapter<SettingsItem, SettingViewHolder>( |
| 40 | private var settings: ArrayList<SettingsItem>? = null | 39 | AsyncDifferConfig.Builder(DiffCallback()).build() |
| 41 | private var clickedItem: SettingsItem? = null | 40 | ) { |
| 42 | private var clickedPosition: Int | 41 | private val settingsViewModel: SettingsViewModel |
| 43 | private var dialog: AlertDialog? = null | 42 | get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java] |
| 44 | private var sliderProgress = 0 | ||
| 45 | private var textSliderValue: TextView? = null | ||
| 46 | |||
| 47 | private var defaultCancelListener = | ||
| 48 | DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } | ||
| 49 | 43 | ||
| 50 | init { | 44 | init { |
| 51 | clickedPosition = -1 | 45 | fragment.viewLifecycleOwner.lifecycleScope.launch { |
| 46 | fragment.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 47 | settingsViewModel.adapterItemChanged.collect { | ||
| 48 | if (it != -1) { | ||
| 49 | notifyItemChanged(it) | ||
| 50 | settingsViewModel.setAdapterItemChanged(-1) | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 52 | } | 55 | } |
| 53 | 56 | ||
| 54 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { | 57 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { |
| @@ -90,67 +93,41 @@ class SettingsAdapter( | |||
| 90 | } | 93 | } |
| 91 | 94 | ||
| 92 | override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { | 95 | override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { |
| 93 | holder.bind(getItem(position)) | 96 | holder.bind(currentList[position]) |
| 94 | } | 97 | } |
| 95 | 98 | ||
| 96 | private fun getItem(position: Int): SettingsItem { | 99 | override fun getItemCount(): Int = currentList.size |
| 97 | return settings!![position] | ||
| 98 | } | ||
| 99 | |||
| 100 | override fun getItemCount(): Int { | ||
| 101 | return if (settings != null) { | ||
| 102 | settings!!.size | ||
| 103 | } else { | ||
| 104 | 0 | ||
| 105 | } | ||
| 106 | } | ||
| 107 | 100 | ||
| 108 | override fun getItemViewType(position: Int): Int { | 101 | override fun getItemViewType(position: Int): Int { |
| 109 | return getItem(position).type | 102 | return currentList[position].type |
| 110 | } | ||
| 111 | |||
| 112 | fun setSettingsList(settings: ArrayList<SettingsItem>?) { | ||
| 113 | this.settings = settings | ||
| 114 | notifyDataSetChanged() | ||
| 115 | } | ||
| 116 | |||
| 117 | fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { | ||
| 118 | val setting = item.setChecked(checked) | ||
| 119 | fragmentView.putSetting(setting) | ||
| 120 | fragmentView.onSettingChanged() | ||
| 121 | } | 103 | } |
| 122 | 104 | ||
| 123 | private fun onSingleChoiceClick(item: SingleChoiceSetting) { | 105 | fun onBooleanClick(item: SwitchSetting, checked: Boolean) { |
| 124 | clickedItem = item | 106 | item.checked = checked |
| 125 | val value = getSelectionForSingleChoiceValue(item) | 107 | settingsViewModel.setShouldReloadSettingsList(true) |
| 126 | dialog = MaterialAlertDialogBuilder(context) | 108 | settingsViewModel.shouldSave = true |
| 127 | .setTitle(item.nameId) | ||
| 128 | .setSingleChoiceItems(item.choicesId, value, this) | ||
| 129 | .show() | ||
| 130 | } | 109 | } |
| 131 | 110 | ||
| 132 | fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { | 111 | fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { |
| 133 | clickedPosition = position | 112 | SettingsDialogFragment.newInstance( |
| 134 | onSingleChoiceClick(item) | 113 | settingsViewModel, |
| 135 | } | 114 | item, |
| 136 | 115 | SettingsItem.TYPE_SINGLE_CHOICE, | |
| 137 | private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { | 116 | position |
| 138 | clickedItem = item | 117 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
| 139 | dialog = MaterialAlertDialogBuilder(context) | ||
| 140 | .setTitle(item.nameId) | ||
| 141 | .setSingleChoiceItems(item.choices, item.selectValueIndex, this) | ||
| 142 | .show() | ||
| 143 | } | 118 | } |
| 144 | 119 | ||
| 145 | fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { | 120 | fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { |
| 146 | clickedPosition = position | 121 | SettingsDialogFragment.newInstance( |
| 147 | onStringSingleChoiceClick(item) | 122 | settingsViewModel, |
| 123 | item, | ||
| 124 | SettingsItem.TYPE_STRING_SINGLE_CHOICE, | ||
| 125 | position | ||
| 126 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) | ||
| 148 | } | 127 | } |
| 149 | 128 | ||
| 150 | fun onDateTimeClick(item: DateTimeSetting, position: Int) { | 129 | fun onDateTimeClick(item: DateTimeSetting, position: Int) { |
| 151 | clickedItem = item | 130 | val storedTime = item.value * 1000 |
| 152 | clickedPosition = position | ||
| 153 | val storedTime = java.lang.Long.decode(item.value) * 1000 | ||
| 154 | 131 | ||
| 155 | // Helper to extract hour and minute from epoch time | 132 | // Helper to extract hour and minute from epoch time |
| 156 | val calendar: Calendar = Calendar.getInstance() | 133 | val calendar: Calendar = Calendar.getInstance() |
| @@ -158,7 +135,7 @@ class SettingsAdapter( | |||
| 158 | calendar.timeZone = TimeZone.getTimeZone("UTC") | 135 | calendar.timeZone = TimeZone.getTimeZone("UTC") |
| 159 | 136 | ||
| 160 | var timeFormat: Int = TimeFormat.CLOCK_12H | 137 | var timeFormat: Int = TimeFormat.CLOCK_12H |
| 161 | if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) { | 138 | if (DateFormat.is24HourFormat(context)) { |
| 162 | timeFormat = TimeFormat.CLOCK_24H | 139 | timeFormat = TimeFormat.CLOCK_24H |
| 163 | } | 140 | } |
| 164 | 141 | ||
| @@ -175,7 +152,7 @@ class SettingsAdapter( | |||
| 175 | 152 | ||
| 176 | datePicker.addOnPositiveButtonClickListener { | 153 | datePicker.addOnPositiveButtonClickListener { |
| 177 | timePicker.show( | 154 | timePicker.show( |
| 178 | (fragmentView.activityView as AppCompatActivity).supportFragmentManager, | 155 | fragment.childFragmentManager, |
| 179 | "TimePicker" | 156 | "TimePicker" |
| 180 | ) | 157 | ) |
| 181 | } | 158 | } |
| @@ -183,157 +160,50 @@ class SettingsAdapter( | |||
| 183 | var epochTime: Long = datePicker.selection!! / 1000 | 160 | var epochTime: Long = datePicker.selection!! / 1000 |
| 184 | epochTime += timePicker.hour.toLong() * 60 * 60 | 161 | epochTime += timePicker.hour.toLong() * 60 * 60 |
| 185 | epochTime += timePicker.minute.toLong() * 60 | 162 | epochTime += timePicker.minute.toLong() * 60 |
| 186 | val rtcString = epochTime.toString() | 163 | if (item.value != epochTime) { |
| 187 | if (item.value != rtcString) { | 164 | settingsViewModel.shouldSave = true |
| 188 | fragmentView.onSettingChanged() | 165 | notifyItemChanged(position) |
| 166 | item.value = epochTime | ||
| 189 | } | 167 | } |
| 190 | notifyItemChanged(clickedPosition) | ||
| 191 | val setting = item.setSelectedValue(rtcString) | ||
| 192 | fragmentView.putSetting(setting) | ||
| 193 | clickedItem = null | ||
| 194 | } | 168 | } |
| 195 | datePicker.show( | 169 | datePicker.show( |
| 196 | (fragmentView.activityView as AppCompatActivity).supportFragmentManager, | 170 | fragment.childFragmentManager, |
| 197 | "DatePicker" | 171 | "DatePicker" |
| 198 | ) | 172 | ) |
| 199 | } | 173 | } |
| 200 | 174 | ||
| 201 | fun onSliderClick(item: SliderSetting, position: Int) { | 175 | fun onSliderClick(item: SliderSetting, position: Int) { |
| 202 | clickedItem = item | 176 | SettingsDialogFragment.newInstance( |
| 203 | clickedPosition = position | 177 | settingsViewModel, |
| 204 | sliderProgress = item.selectedValue | 178 | item, |
| 205 | 179 | SettingsItem.TYPE_SLIDER, | |
| 206 | val inflater = LayoutInflater.from(context) | 180 | position |
| 207 | val sliderBinding = DialogSliderBinding.inflate(inflater) | 181 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
| 208 | |||
| 209 | textSliderValue = sliderBinding.textValue | ||
| 210 | textSliderValue!!.text = sliderProgress.toString() | ||
| 211 | sliderBinding.textUnits.text = item.units | ||
| 212 | |||
| 213 | sliderBinding.slider.apply { | ||
| 214 | valueFrom = item.min.toFloat() | ||
| 215 | valueTo = item.max.toFloat() | ||
| 216 | value = sliderProgress.toFloat() | ||
| 217 | addOnChangeListener { _: Slider, value: Float, _: Boolean -> | ||
| 218 | sliderProgress = value.toInt() | ||
| 219 | textSliderValue!!.text = sliderProgress.toString() | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | dialog = MaterialAlertDialogBuilder(context) | ||
| 224 | .setTitle(item.nameId) | ||
| 225 | .setView(sliderBinding.root) | ||
| 226 | .setPositiveButton(android.R.string.ok, this) | ||
| 227 | .setNegativeButton(android.R.string.cancel, defaultCancelListener) | ||
| 228 | .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int -> | ||
| 229 | sliderBinding.slider.value = item.defaultValue!!.toFloat() | ||
| 230 | onClick(dialog, which) | ||
| 231 | } | ||
| 232 | .show() | ||
| 233 | } | 182 | } |
| 234 | 183 | ||
| 235 | fun onSubmenuClick(item: SubmenuSetting) { | 184 | fun onSubmenuClick(item: SubmenuSetting) { |
| 236 | fragmentView.loadSubMenu(item.menuKey) | 185 | val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null) |
| 186 | fragment.view?.findNavController()?.navigate(action) | ||
| 237 | } | 187 | } |
| 238 | 188 | ||
| 239 | override fun onClick(dialog: DialogInterface, which: Int) { | 189 | fun onLongClick(item: SettingsItem, position: Int): Boolean { |
| 240 | when (clickedItem) { | 190 | SettingsDialogFragment.newInstance( |
| 241 | is SingleChoiceSetting -> { | 191 | settingsViewModel, |
| 242 | val scSetting = clickedItem as SingleChoiceSetting | 192 | item, |
| 243 | val value = getValueForSingleChoiceSelection(scSetting, which) | 193 | SettingsDialogFragment.TYPE_RESET_SETTING, |
| 244 | if (scSetting.selectedValue != value) { | 194 | position |
| 245 | fragmentView.onSettingChanged() | 195 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
| 246 | } | ||
| 247 | |||
| 248 | // Get the backing Setting, which may be null (if for example it was missing from the file) | ||
| 249 | val setting = scSetting.setSelectedValue(value) | ||
| 250 | fragmentView.putSetting(setting) | ||
| 251 | closeDialog() | ||
| 252 | } | ||
| 253 | |||
| 254 | is StringSingleChoiceSetting -> { | ||
| 255 | val scSetting = clickedItem as StringSingleChoiceSetting | ||
| 256 | val value = scSetting.getValueAt(which) | ||
| 257 | if (scSetting.selectedValue != value) fragmentView.onSettingChanged() | ||
| 258 | val setting = scSetting.setSelectedValue(value!!) | ||
| 259 | fragmentView.putSetting(setting) | ||
| 260 | closeDialog() | ||
| 261 | } | ||
| 262 | |||
| 263 | is SliderSetting -> { | ||
| 264 | val sliderSetting = clickedItem as SliderSetting | ||
| 265 | if (sliderSetting.selectedValue != sliderProgress) { | ||
| 266 | fragmentView.onSettingChanged() | ||
| 267 | } | ||
| 268 | if (sliderSetting.setting is FloatSetting) { | ||
| 269 | val value = sliderProgress.toFloat() | ||
| 270 | val setting = sliderSetting.setSelectedValue(value) | ||
| 271 | fragmentView.putSetting(setting) | ||
| 272 | } else { | ||
| 273 | val setting = sliderSetting.setSelectedValue(sliderProgress) | ||
| 274 | fragmentView.putSetting(setting) | ||
| 275 | } | ||
| 276 | closeDialog() | ||
| 277 | } | ||
| 278 | } | ||
| 279 | clickedItem = null | ||
| 280 | sliderProgress = -1 | ||
| 281 | } | ||
| 282 | |||
| 283 | fun onLongClick(setting: AbstractSetting, position: Int): Boolean { | ||
| 284 | MaterialAlertDialogBuilder(context) | ||
| 285 | .setMessage(R.string.reset_setting_confirmation) | ||
| 286 | .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> | ||
| 287 | when (setting) { | ||
| 288 | is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean | ||
| 289 | is AbstractFloatSetting -> setting.float = setting.defaultValue as Float | ||
| 290 | is AbstractIntSetting -> setting.int = setting.defaultValue as Int | ||
| 291 | is AbstractStringSetting -> setting.string = setting.defaultValue as String | ||
| 292 | } | ||
| 293 | notifyItemChanged(position) | ||
| 294 | fragmentView.onSettingChanged() | ||
| 295 | } | ||
| 296 | .setNegativeButton(android.R.string.cancel, null) | ||
| 297 | .show() | ||
| 298 | 196 | ||
| 299 | return true | 197 | return true |
| 300 | } | 198 | } |
| 301 | 199 | ||
| 302 | fun closeDialog() { | 200 | private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() { |
| 303 | if (dialog != null) { | 201 | override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { |
| 304 | if (clickedPosition != -1) { | 202 | return oldItem.setting.key == newItem.setting.key |
| 305 | notifyItemChanged(clickedPosition) | ||
| 306 | clickedPosition = -1 | ||
| 307 | } | ||
| 308 | dialog!!.dismiss() | ||
| 309 | dialog = null | ||
| 310 | } | 203 | } |
| 311 | } | ||
| 312 | 204 | ||
| 313 | private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { | 205 | override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { |
| 314 | val valuesId = item.valuesId | 206 | return oldItem.setting.key == newItem.setting.key |
| 315 | return if (valuesId > 0) { | ||
| 316 | val valuesArray = context.resources.getIntArray(valuesId) | ||
| 317 | valuesArray[which] | ||
| 318 | } else { | ||
| 319 | which | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { | ||
| 324 | val value = item.selectedValue | ||
| 325 | val valuesId = item.valuesId | ||
| 326 | if (valuesId > 0) { | ||
| 327 | val valuesArray = context.resources.getIntArray(valuesId) | ||
| 328 | for (index in valuesArray.indices) { | ||
| 329 | val current = valuesArray[index] | ||
| 330 | if (current == value) { | ||
| 331 | return index | ||
| 332 | } | ||
| 333 | } | ||
| 334 | } else { | ||
| 335 | return value | ||
| 336 | } | 207 | } |
| 337 | return -1 | ||
| 338 | } | 208 | } |
| 339 | } | 209 | } |
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 70a74c4dd..70d8ec14b 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 | |||
| @@ -3,40 +3,49 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.annotation.SuppressLint |
| 7 | import android.os.Bundle | 7 | import android.os.Bundle |
| 8 | import android.view.LayoutInflater | 8 | import android.view.LayoutInflater |
| 9 | import android.view.View | 9 | import android.view.View |
| 10 | import android.view.ViewGroup | 10 | import android.view.ViewGroup |
| 11 | import android.view.ViewGroup.MarginLayoutParams | ||
| 11 | import androidx.core.view.ViewCompat | 12 | import androidx.core.view.ViewCompat |
| 12 | import androidx.core.view.WindowInsetsCompat | 13 | import androidx.core.view.WindowInsetsCompat |
| 13 | import androidx.core.view.updatePadding | 14 | import androidx.core.view.updatePadding |
| 14 | import androidx.fragment.app.Fragment | 15 | import androidx.fragment.app.Fragment |
| 16 | import androidx.fragment.app.activityViewModels | ||
| 17 | import androidx.lifecycle.Lifecycle | ||
| 18 | import androidx.lifecycle.lifecycleScope | ||
| 19 | import androidx.lifecycle.repeatOnLifecycle | ||
| 20 | import androidx.navigation.findNavController | ||
| 21 | import androidx.navigation.fragment.navArgs | ||
| 15 | import androidx.recyclerview.widget.LinearLayoutManager | 22 | import androidx.recyclerview.widget.LinearLayoutManager |
| 16 | import com.google.android.material.divider.MaterialDividerItemDecoration | 23 | import com.google.android.material.divider.MaterialDividerItemDecoration |
| 24 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 25 | import kotlinx.coroutines.flow.collectLatest | ||
| 26 | import kotlinx.coroutines.launch | ||
| 27 | import org.yuzu.yuzu_emu.R | ||
| 17 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding | 28 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 29 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 19 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 30 | import org.yuzu.yuzu_emu.model.SettingsViewModel |
| 20 | 31 | ||
| 21 | class SettingsFragment : Fragment(), SettingsFragmentView { | 32 | class SettingsFragment : Fragment() { |
| 22 | override var activityView: SettingsActivityView? = null | 33 | private lateinit var presenter: SettingsFragmentPresenter |
| 23 | |||
| 24 | private val fragmentPresenter = SettingsFragmentPresenter(this) | ||
| 25 | private var settingsAdapter: SettingsAdapter? = null | 34 | private var settingsAdapter: SettingsAdapter? = null |
| 26 | 35 | ||
| 27 | private var _binding: FragmentSettingsBinding? = null | 36 | private var _binding: FragmentSettingsBinding? = null |
| 28 | private val binding get() = _binding!! | 37 | private val binding get() = _binding!! |
| 29 | 38 | ||
| 30 | override fun onAttach(context: Context) { | 39 | private val args by navArgs<SettingsFragmentArgs>() |
| 31 | super.onAttach(context) | 40 | |
| 32 | activityView = requireActivity() as SettingsActivityView | 41 | private val settingsViewModel: SettingsViewModel by activityViewModels() |
| 33 | } | ||
| 34 | 42 | ||
| 35 | override fun onCreate(savedInstanceState: Bundle?) { | 43 | override fun onCreate(savedInstanceState: Bundle?) { |
| 36 | super.onCreate(savedInstanceState) | 44 | super.onCreate(savedInstanceState) |
| 37 | val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) | 45 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
| 38 | val gameId = requireArguments().getString(ARGUMENT_GAME_ID) | 46 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
| 39 | fragmentPresenter.onCreate(menuTag!!, gameId!!) | 47 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
| 48 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 40 | } | 49 | } |
| 41 | 50 | ||
| 42 | override fun onCreateView( | 51 | override fun onCreateView( |
| @@ -48,8 +57,17 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 48 | return binding.root | 57 | return binding.root |
| 49 | } | 58 | } |
| 50 | 59 | ||
| 60 | // This is using the correct scope, lint is just acting up | ||
| 61 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
| 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) | 63 | settingsAdapter = SettingsAdapter(this, requireContext()) |
| 64 | presenter = SettingsFragmentPresenter( | ||
| 65 | settingsViewModel, | ||
| 66 | settingsAdapter!!, | ||
| 67 | args.menuTag | ||
| 68 | ) | ||
| 69 | |||
| 70 | binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId) | ||
| 53 | val dividerDecoration = MaterialDividerItemDecoration( | 71 | val dividerDecoration = MaterialDividerItemDecoration( |
| 54 | requireContext(), | 72 | requireContext(), |
| 55 | LinearLayoutManager.VERTICAL | 73 | LinearLayoutManager.VERTICAL |
| @@ -57,71 +75,89 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 57 | dividerDecoration.isLastItemDecorated = false | 75 | dividerDecoration.isLastItemDecorated = false |
| 58 | binding.listSettings.apply { | 76 | binding.listSettings.apply { |
| 59 | adapter = settingsAdapter | 77 | adapter = settingsAdapter |
| 60 | layoutManager = LinearLayoutManager(activity) | 78 | layoutManager = LinearLayoutManager(requireContext()) |
| 61 | addItemDecoration(dividerDecoration) | 79 | addItemDecoration(dividerDecoration) |
| 62 | } | 80 | } |
| 63 | fragmentPresenter.onViewCreated() | ||
| 64 | 81 | ||
| 65 | setInsets() | 82 | binding.toolbarSettings.setNavigationOnClickListener { |
| 66 | } | 83 | settingsViewModel.setShouldNavigateBack(true) |
| 67 | |||
| 68 | override fun onDetach() { | ||
| 69 | super.onDetach() | ||
| 70 | activityView = null | ||
| 71 | if (settingsAdapter != null) { | ||
| 72 | settingsAdapter!!.closeDialog() | ||
| 73 | } | 84 | } |
| 74 | } | ||
| 75 | |||
| 76 | override fun showSettingsList(settingsList: ArrayList<SettingsItem>) { | ||
| 77 | settingsAdapter!!.setSettingsList(settingsList) | ||
| 78 | } | ||
| 79 | 85 | ||
| 80 | override fun loadSettingsList() { | 86 | viewLifecycleOwner.lifecycleScope.apply { |
| 81 | fragmentPresenter.loadSettingsList() | 87 | launch { |
| 82 | } | 88 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 89 | settingsViewModel.shouldReloadSettingsList.collectLatest { | ||
| 90 | if (it) { | ||
| 91 | settingsViewModel.setShouldReloadSettingsList(false) | ||
| 92 | presenter.loadSettingsList() | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | launch { | ||
| 98 | settingsViewModel.isUsingSearch.collectLatest { | ||
| 99 | if (it) { | ||
| 100 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) | ||
| 101 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) | ||
| 102 | } else { | ||
| 103 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 104 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 83 | 109 | ||
| 84 | override fun loadSubMenu(menuKey: String) { | 110 | if (args.menuTag == Settings.MenuTag.SECTION_ROOT) { |
| 85 | activityView!!.showSettingsFragment( | 111 | binding.toolbarSettings.inflateMenu(R.menu.menu_settings) |
| 86 | menuKey, | 112 | binding.toolbarSettings.setOnMenuItemClickListener { |
| 87 | true, | 113 | when (it.itemId) { |
| 88 | requireArguments().getString(ARGUMENT_GAME_ID)!! | 114 | R.id.action_search -> { |
| 89 | ) | 115 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) |
| 90 | } | 116 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) |
| 117 | view.findNavController() | ||
| 118 | .navigate(R.id.action_settingsFragment_to_settingsSearchFragment) | ||
| 119 | true | ||
| 120 | } | ||
| 121 | |||
| 122 | else -> false | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 91 | 126 | ||
| 92 | override fun showToastMessage(message: String?, is_long: Boolean) { | 127 | presenter.onViewCreated() |
| 93 | activityView!!.showToastMessage(message!!, is_long) | ||
| 94 | } | ||
| 95 | 128 | ||
| 96 | override fun putSetting(setting: AbstractSetting) { | 129 | setInsets() |
| 97 | fragmentPresenter.putSetting(setting) | ||
| 98 | } | 130 | } |
| 99 | 131 | ||
| 100 | override fun onSettingChanged() { | 132 | override fun onResume() { |
| 101 | activityView!!.onSettingChanged() | 133 | super.onResume() |
| 134 | settingsViewModel.setIsUsingSearch(false) | ||
| 102 | } | 135 | } |
| 103 | 136 | ||
| 104 | private fun setInsets() { | 137 | private fun setInsets() { |
| 105 | ViewCompat.setOnApplyWindowInsetsListener( | 138 | ViewCompat.setOnApplyWindowInsetsListener( |
| 106 | binding.listSettings | 139 | binding.root |
| 107 | ) { view: View, windowInsets: WindowInsetsCompat -> | 140 | ) { _: View, windowInsets: WindowInsetsCompat -> |
| 108 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 141 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 109 | view.updatePadding(bottom = insets.bottom) | 142 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 143 | |||
| 144 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 145 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 146 | |||
| 147 | val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) | ||
| 148 | val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams | ||
| 149 | mlpSettingsList.leftMargin = sideMargin + leftInsets | ||
| 150 | mlpSettingsList.rightMargin = sideMargin + rightInsets | ||
| 151 | binding.listSettings.layoutParams = mlpSettingsList | ||
| 152 | binding.listSettings.updatePadding( | ||
| 153 | bottom = barInsets.bottom | ||
| 154 | ) | ||
| 155 | |||
| 156 | val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams | ||
| 157 | mlpAppBar.leftMargin = leftInsets | ||
| 158 | mlpAppBar.rightMargin = rightInsets | ||
| 159 | binding.appbarSettings.layoutParams = mlpAppBar | ||
| 110 | windowInsets | 160 | windowInsets |
| 111 | } | 161 | } |
| 112 | } | 162 | } |
| 113 | |||
| 114 | companion object { | ||
| 115 | private const val ARGUMENT_MENU_TAG = "menu_tag" | ||
| 116 | private const val ARGUMENT_GAME_ID = "game_id" | ||
| 117 | |||
| 118 | fun newInstance(menuTag: String?, gameId: String?): Fragment { | ||
| 119 | val fragment = SettingsFragment() | ||
| 120 | val arguments = Bundle() | ||
| 121 | arguments.putString(ARGUMENT_MENU_TAG, menuTag) | ||
| 122 | arguments.putString(ARGUMENT_GAME_ID, gameId) | ||
| 123 | fragment.arguments = arguments | ||
| 124 | return fragment | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | 163 | } |
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 59c1d9d54..766414a6c 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 | |||
| @@ -3,401 +3,155 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 6 | import android.content.SharedPreferences | 7 | import android.content.SharedPreferences |
| 7 | import android.os.Build | 8 | import android.os.Build |
| 8 | import android.text.TextUtils | 9 | import android.widget.Toast |
| 9 | import androidx.preference.PreferenceManager | 10 | import androidx.preference.PreferenceManager |
| 10 | import org.yuzu.yuzu_emu.R | 11 | import org.yuzu.yuzu_emu.R |
| 11 | import org.yuzu.yuzu_emu.YuzuApplication | 12 | import org.yuzu.yuzu_emu.YuzuApplication |
| 12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 14 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 14 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 15 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| 16 | import org.yuzu.yuzu_emu.features.settings.model.ByteSetting | ||
| 16 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 17 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.LongSetting | ||
| 17 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 19 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 20 | import org.yuzu.yuzu_emu.features.settings.model.ShortSetting |
| 19 | import org.yuzu.yuzu_emu.features.settings.model.view.* | 21 | import org.yuzu.yuzu_emu.features.settings.model.view.* |
| 20 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 22 | import org.yuzu.yuzu_emu.model.SettingsViewModel |
| 21 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment | 23 | import org.yuzu.yuzu_emu.utils.NativeConfig |
| 22 | import org.yuzu.yuzu_emu.utils.ThemeHelper | ||
| 23 | 24 | ||
| 24 | class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { | 25 | class SettingsFragmentPresenter( |
| 25 | private var menuTag: String? = null | 26 | private val settingsViewModel: SettingsViewModel, |
| 26 | private lateinit var gameId: String | 27 | private val adapter: SettingsAdapter, |
| 27 | private var settingsList: ArrayList<SettingsItem>? = null | 28 | private var menuTag: Settings.MenuTag |
| 29 | ) { | ||
| 30 | private var settingsList = ArrayList<SettingsItem>() | ||
| 28 | 31 | ||
| 29 | private val settingsActivity get() = fragmentView.activityView as SettingsActivity | 32 | private val preferences: SharedPreferences |
| 30 | private val settings get() = fragmentView.activityView!!.settings | 33 | get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 31 | 34 | ||
| 32 | private lateinit var preferences: SharedPreferences | 35 | private val context: Context get() = YuzuApplication.appContext |
| 33 | 36 | ||
| 34 | fun onCreate(menuTag: String, gameId: String) { | 37 | // Extension for populating settings list based on paired settings |
| 35 | this.gameId = gameId | 38 | fun ArrayList<SettingsItem>.add(key: String) { |
| 36 | this.menuTag = menuTag | 39 | val item = SettingsItem.settingsItems[key]!! |
| 40 | val pairedSettingKey = item.setting.pairedSettingKey | ||
| 41 | if (pairedSettingKey.isNotEmpty()) { | ||
| 42 | val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) | ||
| 43 | if (!pairedSettingValue) return | ||
| 44 | } | ||
| 45 | add(item) | ||
| 37 | } | 46 | } |
| 38 | 47 | ||
| 39 | fun onViewCreated() { | 48 | fun onViewCreated() { |
| 40 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 41 | loadSettingsList() | 49 | loadSettingsList() |
| 42 | } | 50 | } |
| 43 | 51 | ||
| 44 | fun putSetting(setting: AbstractSetting) { | ||
| 45 | if (setting.section == null || setting.key == null) { | ||
| 46 | return | ||
| 47 | } | ||
| 48 | |||
| 49 | val section = settings.getSection(setting.section!!)!! | ||
| 50 | if (section.getSetting(setting.key!!) == null) { | ||
| 51 | section.putSetting(setting) | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | fun loadSettingsList() { | 52 | fun loadSettingsList() { |
| 56 | if (!TextUtils.isEmpty(gameId)) { | ||
| 57 | settingsActivity.setToolbarTitle("Game Settings: $gameId") | ||
| 58 | } | ||
| 59 | val sl = ArrayList<SettingsItem>() | 53 | val sl = ArrayList<SettingsItem>() |
| 60 | if (menuTag == null) { | ||
| 61 | return | ||
| 62 | } | ||
| 63 | when (menuTag) { | 54 | when (menuTag) { |
| 64 | SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) | 55 | Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl) |
| 65 | Settings.SECTION_GENERAL -> addGeneralSettings(sl) | 56 | Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl) |
| 66 | Settings.SECTION_SYSTEM -> addSystemSettings(sl) | 57 | Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) |
| 67 | Settings.SECTION_RENDERER -> addGraphicsSettings(sl) | 58 | Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) |
| 68 | Settings.SECTION_AUDIO -> addAudioSettings(sl) | 59 | Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl) |
| 69 | Settings.SECTION_THEME -> addThemeSettings(sl) | 60 | Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl) |
| 70 | Settings.SECTION_DEBUG -> addDebugSettings(sl) | 61 | Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl) |
| 71 | else -> { | 62 | else -> { |
| 72 | fragmentView.showToastMessage("Unimplemented menu", false) | 63 | val context = YuzuApplication.appContext |
| 64 | Toast.makeText( | ||
| 65 | context, | ||
| 66 | context.getString(R.string.unimplemented_menu), | ||
| 67 | Toast.LENGTH_SHORT | ||
| 68 | ).show() | ||
| 73 | return | 69 | return |
| 74 | } | 70 | } |
| 75 | } | 71 | } |
| 76 | settingsList = sl | 72 | settingsList = sl |
| 77 | fragmentView.showSettingsList(settingsList!!) | 73 | adapter.submitList(settingsList) |
| 78 | } | 74 | } |
| 79 | 75 | ||
| 80 | private fun addConfigSettings(sl: ArrayList<SettingsItem>) { | 76 | private fun addConfigSettings(sl: ArrayList<SettingsItem>) { |
| 81 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings)) | ||
| 82 | sl.apply { | 77 | sl.apply { |
| 83 | add( | 78 | add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL)) |
| 84 | SubmenuSetting( | 79 | add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM)) |
| 85 | R.string.preferences_general, | 80 | add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER)) |
| 86 | 0, | 81 | add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO)) |
| 87 | Settings.SECTION_GENERAL | 82 | add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG)) |
| 88 | ) | 83 | add( |
| 89 | ) | 84 | RunnableSetting(R.string.reset_to_default, 0, false) { |
| 90 | add( | 85 | settingsViewModel.setShouldShowResetSettingsDialog(true) |
| 91 | SubmenuSetting( | ||
| 92 | R.string.preferences_system, | ||
| 93 | 0, | ||
| 94 | Settings.SECTION_SYSTEM | ||
| 95 | ) | ||
| 96 | ) | ||
| 97 | add( | ||
| 98 | SubmenuSetting( | ||
| 99 | R.string.preferences_graphics, | ||
| 100 | 0, | ||
| 101 | Settings.SECTION_RENDERER | ||
| 102 | ) | ||
| 103 | ) | ||
| 104 | add( | ||
| 105 | SubmenuSetting( | ||
| 106 | R.string.preferences_audio, | ||
| 107 | 0, | ||
| 108 | Settings.SECTION_AUDIO | ||
| 109 | ) | ||
| 110 | ) | ||
| 111 | add( | ||
| 112 | SubmenuSetting( | ||
| 113 | R.string.preferences_debug, | ||
| 114 | 0, | ||
| 115 | Settings.SECTION_DEBUG | ||
| 116 | ) | ||
| 117 | ) | ||
| 118 | add( | ||
| 119 | RunnableSetting( | ||
| 120 | R.string.reset_to_default, | ||
| 121 | 0, | ||
| 122 | false | ||
| 123 | ) { | ||
| 124 | ResetSettingsDialogFragment().show( | ||
| 125 | settingsActivity.supportFragmentManager, | ||
| 126 | ResetSettingsDialogFragment.TAG | ||
| 127 | ) | ||
| 128 | } | 86 | } |
| 129 | ) | 87 | ) |
| 130 | } | 88 | } |
| 131 | } | 89 | } |
| 132 | 90 | ||
| 133 | private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { | 91 | private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { |
| 134 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) | ||
| 135 | sl.apply { | 92 | sl.apply { |
| 136 | add( | 93 | add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) |
| 137 | SwitchSetting( | 94 | add(ShortSetting.RENDERER_SPEED_LIMIT.key) |
| 138 | IntSetting.RENDERER_USE_SPEED_LIMIT, | 95 | add(IntSetting.CPU_ACCURACY.key) |
| 139 | R.string.frame_limit_enable, | 96 | add(BooleanSetting.PICTURE_IN_PICTURE.key) |
| 140 | R.string.frame_limit_enable_description, | ||
| 141 | IntSetting.RENDERER_USE_SPEED_LIMIT.key, | ||
| 142 | IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue | ||
| 143 | ) | ||
| 144 | ) | ||
| 145 | add( | ||
| 146 | SliderSetting( | ||
| 147 | IntSetting.RENDERER_SPEED_LIMIT, | ||
| 148 | R.string.frame_limit_slider, | ||
| 149 | R.string.frame_limit_slider_description, | ||
| 150 | 1, | ||
| 151 | 200, | ||
| 152 | "%", | ||
| 153 | IntSetting.RENDERER_SPEED_LIMIT.key, | ||
| 154 | IntSetting.RENDERER_SPEED_LIMIT.defaultValue | ||
| 155 | ) | ||
| 156 | ) | ||
| 157 | add( | ||
| 158 | SingleChoiceSetting( | ||
| 159 | IntSetting.CPU_ACCURACY, | ||
| 160 | R.string.cpu_accuracy, | ||
| 161 | 0, | ||
| 162 | R.array.cpuAccuracyNames, | ||
| 163 | R.array.cpuAccuracyValues, | ||
| 164 | IntSetting.CPU_ACCURACY.key, | ||
| 165 | IntSetting.CPU_ACCURACY.defaultValue | ||
| 166 | ) | ||
| 167 | ) | ||
| 168 | add( | ||
| 169 | SwitchSetting( | ||
| 170 | BooleanSetting.PICTURE_IN_PICTURE, | ||
| 171 | R.string.picture_in_picture, | ||
| 172 | R.string.picture_in_picture_description, | ||
| 173 | BooleanSetting.PICTURE_IN_PICTURE.key, | ||
| 174 | BooleanSetting.PICTURE_IN_PICTURE.defaultValue | ||
| 175 | ) | ||
| 176 | ) | ||
| 177 | } | 97 | } |
| 178 | } | 98 | } |
| 179 | 99 | ||
| 180 | private fun addSystemSettings(sl: ArrayList<SettingsItem>) { | 100 | private fun addSystemSettings(sl: ArrayList<SettingsItem>) { |
| 181 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) | ||
| 182 | sl.apply { | 101 | sl.apply { |
| 183 | add( | 102 | add(BooleanSetting.USE_DOCKED_MODE.key) |
| 184 | SwitchSetting( | 103 | add(IntSetting.REGION_INDEX.key) |
| 185 | IntSetting.USE_DOCKED_MODE, | 104 | add(IntSetting.LANGUAGE_INDEX.key) |
| 186 | R.string.use_docked_mode, | 105 | add(BooleanSetting.USE_CUSTOM_RTC.key) |
| 187 | R.string.use_docked_mode_description, | 106 | add(LongSetting.CUSTOM_RTC.key) |
| 188 | IntSetting.USE_DOCKED_MODE.key, | ||
| 189 | IntSetting.USE_DOCKED_MODE.defaultValue | ||
| 190 | ) | ||
| 191 | ) | ||
| 192 | add( | ||
| 193 | SingleChoiceSetting( | ||
| 194 | IntSetting.REGION_INDEX, | ||
| 195 | R.string.emulated_region, | ||
| 196 | 0, | ||
| 197 | R.array.regionNames, | ||
| 198 | R.array.regionValues, | ||
| 199 | IntSetting.REGION_INDEX.key, | ||
| 200 | IntSetting.REGION_INDEX.defaultValue | ||
| 201 | ) | ||
| 202 | ) | ||
| 203 | add( | ||
| 204 | SingleChoiceSetting( | ||
| 205 | IntSetting.LANGUAGE_INDEX, | ||
| 206 | R.string.emulated_language, | ||
| 207 | 0, | ||
| 208 | R.array.languageNames, | ||
| 209 | R.array.languageValues, | ||
| 210 | IntSetting.LANGUAGE_INDEX.key, | ||
| 211 | IntSetting.LANGUAGE_INDEX.defaultValue | ||
| 212 | ) | ||
| 213 | ) | ||
| 214 | add( | ||
| 215 | SwitchSetting( | ||
| 216 | BooleanSetting.USE_CUSTOM_RTC, | ||
| 217 | R.string.use_custom_rtc, | ||
| 218 | R.string.use_custom_rtc_description, | ||
| 219 | BooleanSetting.USE_CUSTOM_RTC.key, | ||
| 220 | BooleanSetting.USE_CUSTOM_RTC.defaultValue | ||
| 221 | ) | ||
| 222 | ) | ||
| 223 | add( | ||
| 224 | DateTimeSetting( | ||
| 225 | StringSetting.CUSTOM_RTC, | ||
| 226 | R.string.set_custom_rtc, | ||
| 227 | 0, | ||
| 228 | StringSetting.CUSTOM_RTC.key, | ||
| 229 | StringSetting.CUSTOM_RTC.defaultValue | ||
| 230 | ) | ||
| 231 | ) | ||
| 232 | } | 107 | } |
| 233 | } | 108 | } |
| 234 | 109 | ||
| 235 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { | 110 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { |
| 236 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) | ||
| 237 | sl.apply { | 111 | sl.apply { |
| 238 | add( | 112 | add(IntSetting.RENDERER_ACCURACY.key) |
| 239 | SingleChoiceSetting( | 113 | add(IntSetting.RENDERER_RESOLUTION.key) |
| 240 | IntSetting.RENDERER_ACCURACY, | 114 | add(IntSetting.RENDERER_VSYNC.key) |
| 241 | R.string.renderer_accuracy, | 115 | add(IntSetting.RENDERER_SCALING_FILTER.key) |
| 242 | 0, | 116 | add(IntSetting.RENDERER_ANTI_ALIASING.key) |
| 243 | R.array.rendererAccuracyNames, | 117 | add(IntSetting.RENDERER_SCREEN_LAYOUT.key) |
| 244 | R.array.rendererAccuracyValues, | 118 | add(IntSetting.RENDERER_ASPECT_RATIO.key) |
| 245 | IntSetting.RENDERER_ACCURACY.key, | 119 | add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) |
| 246 | IntSetting.RENDERER_ACCURACY.defaultValue | 120 | add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) |
| 247 | ) | 121 | add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) |
| 248 | ) | 122 | add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) |
| 249 | add( | ||
| 250 | SingleChoiceSetting( | ||
| 251 | IntSetting.RENDERER_RESOLUTION, | ||
| 252 | R.string.renderer_resolution, | ||
| 253 | 0, | ||
| 254 | R.array.rendererResolutionNames, | ||
| 255 | R.array.rendererResolutionValues, | ||
| 256 | IntSetting.RENDERER_RESOLUTION.key, | ||
| 257 | IntSetting.RENDERER_RESOLUTION.defaultValue | ||
| 258 | ) | ||
| 259 | ) | ||
| 260 | add( | ||
| 261 | SingleChoiceSetting( | ||
| 262 | IntSetting.RENDERER_VSYNC, | ||
| 263 | R.string.renderer_vsync, | ||
| 264 | 0, | ||
| 265 | R.array.rendererVSyncNames, | ||
| 266 | R.array.rendererVSyncValues, | ||
| 267 | IntSetting.RENDERER_VSYNC.key, | ||
| 268 | IntSetting.RENDERER_VSYNC.defaultValue | ||
| 269 | ) | ||
| 270 | ) | ||
| 271 | add( | ||
| 272 | SingleChoiceSetting( | ||
| 273 | IntSetting.RENDERER_SCALING_FILTER, | ||
| 274 | R.string.renderer_scaling_filter, | ||
| 275 | 0, | ||
| 276 | R.array.rendererScalingFilterNames, | ||
| 277 | R.array.rendererScalingFilterValues, | ||
| 278 | IntSetting.RENDERER_SCALING_FILTER.key, | ||
| 279 | IntSetting.RENDERER_SCALING_FILTER.defaultValue | ||
| 280 | ) | ||
| 281 | ) | ||
| 282 | add( | ||
| 283 | SingleChoiceSetting( | ||
| 284 | IntSetting.RENDERER_ANTI_ALIASING, | ||
| 285 | R.string.renderer_anti_aliasing, | ||
| 286 | 0, | ||
| 287 | R.array.rendererAntiAliasingNames, | ||
| 288 | R.array.rendererAntiAliasingValues, | ||
| 289 | IntSetting.RENDERER_ANTI_ALIASING.key, | ||
| 290 | IntSetting.RENDERER_ANTI_ALIASING.defaultValue | ||
| 291 | ) | ||
| 292 | ) | ||
| 293 | add( | ||
| 294 | SingleChoiceSetting( | ||
| 295 | IntSetting.RENDERER_SCREEN_LAYOUT, | ||
| 296 | R.string.renderer_screen_layout, | ||
| 297 | 0, | ||
| 298 | R.array.rendererScreenLayoutNames, | ||
| 299 | R.array.rendererScreenLayoutValues, | ||
| 300 | IntSetting.RENDERER_SCREEN_LAYOUT.key, | ||
| 301 | IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue | ||
| 302 | ) | ||
| 303 | ) | ||
| 304 | add( | ||
| 305 | SingleChoiceSetting( | ||
| 306 | IntSetting.RENDERER_ASPECT_RATIO, | ||
| 307 | R.string.renderer_aspect_ratio, | ||
| 308 | 0, | ||
| 309 | R.array.rendererAspectRatioNames, | ||
| 310 | R.array.rendererAspectRatioValues, | ||
| 311 | IntSetting.RENDERER_ASPECT_RATIO.key, | ||
| 312 | IntSetting.RENDERER_ASPECT_RATIO.defaultValue | ||
| 313 | ) | ||
| 314 | ) | ||
| 315 | add( | ||
| 316 | SwitchSetting( | ||
| 317 | IntSetting.RENDERER_USE_DISK_SHADER_CACHE, | ||
| 318 | R.string.use_disk_shader_cache, | ||
| 319 | R.string.use_disk_shader_cache_description, | ||
| 320 | IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key, | ||
| 321 | IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue | ||
| 322 | ) | ||
| 323 | ) | ||
| 324 | add( | ||
| 325 | SwitchSetting( | ||
| 326 | IntSetting.RENDERER_FORCE_MAX_CLOCK, | ||
| 327 | R.string.renderer_force_max_clock, | ||
| 328 | R.string.renderer_force_max_clock_description, | ||
| 329 | IntSetting.RENDERER_FORCE_MAX_CLOCK.key, | ||
| 330 | IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue | ||
| 331 | ) | ||
| 332 | ) | ||
| 333 | add( | ||
| 334 | SwitchSetting( | ||
| 335 | IntSetting.RENDERER_ASYNCHRONOUS_SHADERS, | ||
| 336 | R.string.renderer_asynchronous_shaders, | ||
| 337 | R.string.renderer_asynchronous_shaders_description, | ||
| 338 | IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key, | ||
| 339 | IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue | ||
| 340 | ) | ||
| 341 | ) | ||
| 342 | add( | ||
| 343 | SwitchSetting( | ||
| 344 | IntSetting.RENDERER_REACTIVE_FLUSHING, | ||
| 345 | R.string.renderer_reactive_flushing, | ||
| 346 | R.string.renderer_reactive_flushing_description, | ||
| 347 | IntSetting.RENDERER_REACTIVE_FLUSHING.key, | ||
| 348 | IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue | ||
| 349 | ) | ||
| 350 | ) | ||
| 351 | } | 123 | } |
| 352 | } | 124 | } |
| 353 | 125 | ||
| 354 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { | 126 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { |
| 355 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) | ||
| 356 | sl.apply { | 127 | sl.apply { |
| 357 | add( | 128 | add(IntSetting.AUDIO_OUTPUT_ENGINE.key) |
| 358 | StringSingleChoiceSetting( | 129 | add(ByteSetting.AUDIO_VOLUME.key) |
| 359 | StringSetting.AUDIO_OUTPUT_ENGINE, | ||
| 360 | R.string.audio_output_engine, | ||
| 361 | 0, | ||
| 362 | settingsActivity.resources.getStringArray(R.array.outputEngineEntries), | ||
| 363 | settingsActivity.resources.getStringArray(R.array.outputEngineValues), | ||
| 364 | StringSetting.AUDIO_OUTPUT_ENGINE.key, | ||
| 365 | StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue | ||
| 366 | ) | ||
| 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 | } | 130 | } |
| 381 | } | 131 | } |
| 382 | 132 | ||
| 383 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { | 133 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { |
| 384 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) | ||
| 385 | sl.apply { | 134 | sl.apply { |
| 386 | val theme: AbstractIntSetting = object : AbstractIntSetting { | 135 | val theme: AbstractIntSetting = object : AbstractIntSetting { |
| 387 | override var int: Int | 136 | override val int: Int |
| 388 | get() = preferences.getInt(Settings.PREF_THEME, 0) | 137 | get() = preferences.getInt(Settings.PREF_THEME, 0) |
| 389 | set(value) { | 138 | |
| 390 | preferences.edit() | 139 | override fun setInt(value: Int) { |
| 391 | .putInt(Settings.PREF_THEME, value) | 140 | preferences.edit() |
| 392 | .apply() | 141 | .putInt(Settings.PREF_THEME, value) |
| 393 | settingsActivity.recreate() | 142 | .apply() |
| 394 | } | 143 | settingsViewModel.setShouldRecreate(true) |
| 395 | override val key: String? = null | 144 | } |
| 396 | override val section: String? = null | 145 | |
| 397 | override val isRuntimeEditable: Boolean = false | 146 | override val key: String = Settings.PREF_THEME |
| 398 | override val valueAsString: String | 147 | override val category = Settings.Category.UiGeneral |
| 399 | get() = preferences.getInt(Settings.PREF_THEME, 0).toString() | 148 | override val isRuntimeModifiable: Boolean = false |
| 400 | override val defaultValue: Any = 0 | 149 | override val defaultValue: Int = 0 |
| 150 | override fun reset() { | ||
| 151 | preferences.edit() | ||
| 152 | .putInt(Settings.PREF_THEME, defaultValue) | ||
| 153 | .apply() | ||
| 154 | } | ||
| 401 | } | 155 | } |
| 402 | 156 | ||
| 403 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | 157 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
| @@ -423,20 +177,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 423 | } | 177 | } |
| 424 | 178 | ||
| 425 | val themeMode: AbstractIntSetting = object : AbstractIntSetting { | 179 | val themeMode: AbstractIntSetting = object : AbstractIntSetting { |
| 426 | override var int: Int | 180 | override val int: Int |
| 427 | get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) | 181 | get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) |
| 428 | set(value) { | 182 | |
| 429 | preferences.edit() | 183 | override fun setInt(value: Int) { |
| 430 | .putInt(Settings.PREF_THEME_MODE, value) | 184 | preferences.edit() |
| 431 | .apply() | 185 | .putInt(Settings.PREF_THEME_MODE, value) |
| 432 | ThemeHelper.setThemeMode(settingsActivity) | 186 | .apply() |
| 433 | } | 187 | settingsViewModel.setShouldRecreate(true) |
| 434 | override val key: String? = null | 188 | } |
| 435 | override val section: String? = null | 189 | |
| 436 | override val isRuntimeEditable: Boolean = false | 190 | override val key: String = Settings.PREF_THEME_MODE |
| 437 | override val valueAsString: String | 191 | override val category = Settings.Category.UiGeneral |
| 438 | get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString() | 192 | override val isRuntimeModifiable: Boolean = false |
| 439 | override val defaultValue: Any = -1 | 193 | override val defaultValue: Int = -1 |
| 194 | override fun reset() { | ||
| 195 | preferences.edit() | ||
| 196 | .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) | ||
| 197 | .apply() | ||
| 198 | settingsViewModel.setShouldRecreate(true) | ||
| 199 | } | ||
| 440 | } | 200 | } |
| 441 | 201 | ||
| 442 | add( | 202 | add( |
| @@ -450,21 +210,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 450 | ) | 210 | ) |
| 451 | 211 | ||
| 452 | val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { | 212 | val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { |
| 453 | override var boolean: Boolean | 213 | override val boolean: Boolean |
| 454 | get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | ||
| 455 | set(value) { | ||
| 456 | preferences.edit() | ||
| 457 | .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) | ||
| 458 | .apply() | ||
| 459 | settingsActivity.recreate() | ||
| 460 | } | ||
| 461 | override val key: String? = null | ||
| 462 | override val section: String? = null | ||
| 463 | override val isRuntimeEditable: Boolean = false | ||
| 464 | override val valueAsString: String | ||
| 465 | get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | 214 | get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) |
| 466 | .toString() | 215 | |
| 467 | override val defaultValue: Any = false | 216 | override fun setBoolean(value: Boolean) { |
| 217 | preferences.edit() | ||
| 218 | .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) | ||
| 219 | .apply() | ||
| 220 | settingsViewModel.setShouldRecreate(true) | ||
| 221 | } | ||
| 222 | |||
| 223 | override val key: String = Settings.PREF_BLACK_BACKGROUNDS | ||
| 224 | override val category = Settings.Category.UiGeneral | ||
| 225 | override val isRuntimeModifiable: Boolean = false | ||
| 226 | override val defaultValue: Boolean = false | ||
| 227 | override fun reset() { | ||
| 228 | preferences.edit() | ||
| 229 | .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) | ||
| 230 | .apply() | ||
| 231 | settingsViewModel.setShouldRecreate(true) | ||
| 232 | } | ||
| 468 | } | 233 | } |
| 469 | 234 | ||
| 470 | add( | 235 | add( |
| @@ -478,62 +243,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 478 | } | 243 | } |
| 479 | 244 | ||
| 480 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { | 245 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { |
| 481 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) | ||
| 482 | sl.apply { | 246 | sl.apply { |
| 483 | add(HeaderSetting(R.string.gpu)) | 247 | add(HeaderSetting(R.string.gpu)) |
| 484 | add( | 248 | add(IntSetting.RENDERER_BACKEND.key) |
| 485 | SingleChoiceSetting( | 249 | add(BooleanSetting.RENDERER_DEBUG.key) |
| 486 | IntSetting.RENDERER_BACKEND, | ||
| 487 | R.string.renderer_api, | ||
| 488 | 0, | ||
| 489 | R.array.rendererApiNames, | ||
| 490 | R.array.rendererApiValues, | ||
| 491 | IntSetting.RENDERER_BACKEND.key, | ||
| 492 | IntSetting.RENDERER_BACKEND.defaultValue | ||
| 493 | ) | ||
| 494 | ) | ||
| 495 | add( | ||
| 496 | SwitchSetting( | ||
| 497 | IntSetting.RENDERER_DEBUG, | ||
| 498 | R.string.renderer_debug, | ||
| 499 | R.string.renderer_debug_description, | ||
| 500 | IntSetting.RENDERER_DEBUG.key, | ||
| 501 | IntSetting.RENDERER_DEBUG.defaultValue | ||
| 502 | ) | ||
| 503 | ) | ||
| 504 | 250 | ||
| 505 | add(HeaderSetting(R.string.cpu)) | 251 | add(HeaderSetting(R.string.cpu)) |
| 506 | add( | 252 | add(BooleanSetting.CPU_DEBUG_MODE.key) |
| 507 | SwitchSetting( | 253 | add(SettingsItem.FASTMEM_COMBINED) |
| 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 | ) | ||
| 537 | } | 254 | } |
| 538 | } | 255 | } |
| 539 | } | 256 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt deleted file mode 100644 index 1ebe35eaa..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt +++ /dev/null | |||
| @@ -1,58 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Abstraction for a screen showing a list of settings. Instances of | ||
| 11 | * this type of view will each display a layer of the setting hierarchy. | ||
| 12 | */ | ||
| 13 | interface SettingsFragmentView { | ||
| 14 | /** | ||
| 15 | * Pass an ArrayList to the View so that it can be displayed on screen. | ||
| 16 | * | ||
| 17 | * @param settingsList The result of converting the HashMap to an ArrayList | ||
| 18 | */ | ||
| 19 | fun showSettingsList(settingsList: ArrayList<SettingsItem>) | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Instructs the Fragment to load the settings screen. | ||
| 23 | */ | ||
| 24 | fun loadSettingsList() | ||
| 25 | |||
| 26 | /** | ||
| 27 | * @return The Fragment's containing activity. | ||
| 28 | */ | ||
| 29 | val activityView: SettingsActivityView? | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Tell the Fragment to tell the containing Activity to show a new | ||
| 33 | * Fragment containing a submenu of settings. | ||
| 34 | * | ||
| 35 | * @param menuKey Identifier for the settings group that should be shown. | ||
| 36 | */ | ||
| 37 | fun loadSubMenu(menuKey: String) | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Tell the Fragment to tell the containing activity to display a toast message. | ||
| 41 | * | ||
| 42 | * @param message Text to be shown in the Toast | ||
| 43 | * @param is_long Whether this should be a long Toast or short one. | ||
| 44 | */ | ||
| 45 | fun showToastMessage(message: String?, is_long: Boolean) | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Have the fragment add a setting to the HashMap. | ||
| 49 | * | ||
| 50 | * @param setting The (possibly previously missing) new setting. | ||
| 51 | */ | ||
| 52 | fun putSetting(setting: AbstractSetting) | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Have the fragment tell the containing Activity that a setting was modified. | ||
| 56 | */ | ||
| 57 | fun onSettingChanged() | ||
| 58 | } | ||
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 7955532ee..525f013f8 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 | |||
| @@ -25,12 +25,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
| 25 | binding.textSettingDescription.setText(item.descriptionId) | 25 | binding.textSettingDescription.setText(item.descriptionId) |
| 26 | binding.textSettingDescription.visibility = View.VISIBLE | 26 | binding.textSettingDescription.visibility = View.VISIBLE |
| 27 | } else { | 27 | } else { |
| 28 | val epochTime = setting.value.toLong() | 28 | binding.textSettingDescription.visibility = View.GONE |
| 29 | val instant = Instant.ofEpochMilli(epochTime * 1000) | ||
| 30 | val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) | ||
| 31 | val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) | ||
| 32 | binding.textSettingDescription.text = dateFormatter.format(zonedTime) | ||
| 33 | } | 29 | } |
| 30 | |||
| 31 | binding.textSettingValue.visibility = View.VISIBLE | ||
| 32 | val epochTime = setting.value | ||
| 33 | val instant = Instant.ofEpochMilli(epochTime * 1000) | ||
| 34 | val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) | ||
| 35 | val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) | ||
| 36 | binding.textSettingValue.text = dateFormatter.format(zonedTime) | ||
| 37 | |||
| 38 | setStyle(setting.isEditable, binding) | ||
| 34 | } | 39 | } |
| 35 | 40 | ||
| 36 | override fun onClick(clicked: View) { | 41 | override fun onClick(clicked: View) { |
| @@ -41,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
| 41 | 46 | ||
| 42 | override fun onLongClick(clicked: View): Boolean { | 47 | override fun onLongClick(clicked: View): Boolean { |
| 43 | if (setting.isEditable) { | 48 | if (setting.isEditable) { |
| 44 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 49 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 45 | } | 50 | } |
| 46 | return false | 51 | return false |
| 47 | } | 52 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt index 5dad5945f..83a2e94f1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt | |||
| @@ -23,6 +23,9 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
| 23 | } else { | 23 | } else { |
| 24 | binding.textSettingDescription.visibility = View.GONE | 24 | binding.textSettingDescription.visibility = View.GONE |
| 25 | } | 25 | } |
| 26 | binding.textSettingValue.visibility = View.GONE | ||
| 27 | |||
| 28 | setStyle(setting.isEditable, binding) | ||
| 26 | } | 29 | } |
| 27 | 30 | ||
| 28 | override fun onClick(clicked: View) { | 31 | override fun onClick(clicked: View) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt index f56460893..0fd1d2eaa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt | |||
| @@ -5,6 +5,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder | |||
| 5 | 5 | ||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import androidx.recyclerview.widget.RecyclerView | 7 | import androidx.recyclerview.widget.RecyclerView |
| 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 9 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | ||
| 8 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
| 9 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
| 10 | 12 | ||
| @@ -33,4 +35,18 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings | |||
| 33 | abstract override fun onClick(clicked: View) | 35 | abstract override fun onClick(clicked: View) |
| 34 | 36 | ||
| 35 | abstract override fun onLongClick(clicked: View): Boolean | 37 | abstract override fun onLongClick(clicked: View): Boolean |
| 38 | |||
| 39 | fun setStyle(isEditable: Boolean, binding: ListItemSettingBinding) { | ||
| 40 | val opacity = if (isEditable) 1.0f else 0.5f | ||
| 41 | binding.textSettingName.alpha = opacity | ||
| 42 | binding.textSettingDescription.alpha = opacity | ||
| 43 | binding.textSettingValue.alpha = opacity | ||
| 44 | } | ||
| 45 | |||
| 46 | fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) { | ||
| 47 | binding.switchWidget.isEnabled = isEditable | ||
| 48 | val opacity = if (isEditable) 1.0f else 0.5f | ||
| 49 | binding.textSettingName.alpha = opacity | ||
| 50 | binding.textSettingDescription.alpha = opacity | ||
| 51 | } | ||
| 36 | } | 52 | } |
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 e4e321bd3..80d1b22c1 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 | |||
| @@ -17,28 +17,33 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
| 17 | override fun bind(item: SettingsItem) { | 17 | override fun bind(item: SettingsItem) { |
| 18 | setting = item | 18 | setting = item |
| 19 | binding.textSettingName.setText(item.nameId) | 19 | binding.textSettingName.setText(item.nameId) |
| 20 | binding.textSettingDescription.visibility = View.VISIBLE | ||
| 21 | if (item.descriptionId != 0) { | 20 | if (item.descriptionId != 0) { |
| 22 | binding.textSettingDescription.setText(item.descriptionId) | 21 | binding.textSettingDescription.setText(item.descriptionId) |
| 23 | } else if (item is SingleChoiceSetting) { | 22 | binding.textSettingDescription.visibility = View.VISIBLE |
| 24 | val resMgr = binding.textSettingDescription.context.resources | 23 | } else { |
| 24 | binding.textSettingDescription.visibility = View.GONE | ||
| 25 | } | ||
| 26 | |||
| 27 | binding.textSettingValue.visibility = View.VISIBLE | ||
| 28 | if (item is SingleChoiceSetting) { | ||
| 29 | val resMgr = binding.textSettingValue.context.resources | ||
| 25 | val values = resMgr.getIntArray(item.valuesId) | 30 | val values = resMgr.getIntArray(item.valuesId) |
| 26 | for (i in values.indices) { | 31 | for (i in values.indices) { |
| 27 | if (values[i] == item.selectedValue) { | 32 | if (values[i] == item.selectedValue) { |
| 28 | binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] | 33 | binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] |
| 29 | return | 34 | break |
| 30 | } | 35 | } |
| 31 | } | 36 | } |
| 32 | } else if (item is StringSingleChoiceSetting) { | 37 | } else if (item is StringSingleChoiceSetting) { |
| 33 | for (i in item.values!!.indices) { | 38 | for (i in item.values.indices) { |
| 34 | if (item.values[i] == item.selectedValue) { | 39 | if (item.values[i] == item.selectedValue) { |
| 35 | binding.textSettingDescription.text = item.choices[i] | 40 | binding.textSettingValue.text = item.choices[i] |
| 36 | return | 41 | break |
| 37 | } | 42 | } |
| 38 | } | 43 | } |
| 39 | } else { | ||
| 40 | binding.textSettingDescription.visibility = View.GONE | ||
| 41 | } | 44 | } |
| 45 | |||
| 46 | setStyle(setting.isEditable, binding) | ||
| 42 | } | 47 | } |
| 43 | 48 | ||
| 44 | override fun onClick(clicked: View) { | 49 | override fun onClick(clicked: View) { |
| @@ -61,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
| 61 | 66 | ||
| 62 | override fun onLongClick(clicked: View): Boolean { | 67 | override fun onLongClick(clicked: View): Boolean { |
| 63 | if (setting.isEditable) { | 68 | if (setting.isEditable) { |
| 64 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 69 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 65 | } | 70 | } |
| 66 | return false | 71 | return false |
| 67 | } | 72 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt index cc3f39aa5..b83c90100 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt | |||
| @@ -4,6 +4,7 @@ | |||
| 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.R | ||
| 7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting |
| @@ -22,6 +23,14 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda | |||
| 22 | } else { | 23 | } else { |
| 23 | binding.textSettingDescription.visibility = View.GONE | 24 | binding.textSettingDescription.visibility = View.GONE |
| 24 | } | 25 | } |
| 26 | binding.textSettingValue.visibility = View.VISIBLE | ||
| 27 | binding.textSettingValue.text = String.format( | ||
| 28 | binding.textSettingValue.context.getString(R.string.value_with_units), | ||
| 29 | setting.selectedValue, | ||
| 30 | setting.units | ||
| 31 | ) | ||
| 32 | |||
| 33 | setStyle(setting.isEditable, binding) | ||
| 25 | } | 34 | } |
| 26 | 35 | ||
| 27 | override fun onClick(clicked: View) { | 36 | override fun onClick(clicked: View) { |
| @@ -32,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda | |||
| 32 | 41 | ||
| 33 | override fun onLongClick(clicked: View): Boolean { | 42 | override fun onLongClick(clicked: View): Boolean { |
| 34 | if (setting.isEditable) { | 43 | if (setting.isEditable) { |
| 35 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 44 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 36 | } | 45 | } |
| 37 | return false | 46 | return false |
| 38 | } | 47 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt index c545b4174..1cf581a9d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt | |||
| @@ -22,6 +22,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd | |||
| 22 | } else { | 22 | } else { |
| 23 | binding.textSettingDescription.visibility = View.GONE | 23 | binding.textSettingDescription.visibility = View.GONE |
| 24 | } | 24 | } |
| 25 | binding.textSettingValue.visibility = View.GONE | ||
| 25 | } | 26 | } |
| 26 | 27 | ||
| 27 | override fun onClick(clicked: View) { | 28 | override fun onClick(clicked: View) { |
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 54f531795..57fdeaa20 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 | |||
| @@ -25,12 +25,14 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter | |||
| 25 | binding.textSettingDescription.text = "" | 25 | binding.textSettingDescription.text = "" |
| 26 | binding.textSettingDescription.visibility = View.GONE | 26 | binding.textSettingDescription.visibility = View.GONE |
| 27 | } | 27 | } |
| 28 | binding.switchWidget.isChecked = setting.isChecked | 28 | |
| 29 | binding.switchWidget.setOnCheckedChangeListener(null) | ||
| 30 | binding.switchWidget.isChecked = setting.checked | ||
| 29 | binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> | 31 | binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> |
| 30 | adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) | 32 | adapter.onBooleanClick(item, binding.switchWidget.isChecked) |
| 31 | } | 33 | } |
| 32 | 34 | ||
| 33 | binding.switchWidget.isEnabled = setting.isEditable | 35 | setStyle(setting.isEditable, binding) |
| 34 | } | 36 | } |
| 35 | 37 | ||
| 36 | override fun onClick(clicked: View) { | 38 | override fun onClick(clicked: View) { |
| @@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter | |||
| 41 | 43 | ||
| 42 | override fun onLongClick(clicked: View): Boolean { | 44 | override fun onLongClick(clicked: View): Boolean { |
| 43 | if (setting.isEditable) { | 45 | if (setting.isEditable) { |
| 44 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 46 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 45 | } | 47 | } |
| 46 | return false | 48 | return false |
| 47 | } | 49 | } |
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 70a52df5d..2b04d666a 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,18 +3,15 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.utils | 4 | package org.yuzu.yuzu_emu.features.settings.utils |
| 5 | 5 | ||
| 6 | import android.widget.Toast | ||
| 6 | import java.io.* | 7 | import java.io.* |
| 7 | import java.util.* | ||
| 8 | import org.ini4j.Wini | 8 | import org.ini4j.Wini |
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 10 | import org.yuzu.yuzu_emu.R | 9 | import org.yuzu.yuzu_emu.R |
| 11 | import org.yuzu.yuzu_emu.YuzuApplication | 10 | import org.yuzu.yuzu_emu.YuzuApplication |
| 12 | import org.yuzu.yuzu_emu.features.settings.model.* | 11 | import org.yuzu.yuzu_emu.features.settings.model.* |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | ||
| 15 | import org.yuzu.yuzu_emu.utils.BiMap | ||
| 16 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 12 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 17 | import org.yuzu.yuzu_emu.utils.Log | 13 | import org.yuzu.yuzu_emu.utils.Log |
| 14 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 18 | 15 | ||
| 19 | /** | 16 | /** |
| 20 | * Contains static methods for interacting with .ini files in which settings are stored. | 17 | * Contains static methods for interacting with .ini files in which settings are stored. |
| @@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log | |||
| 22 | object SettingsFile { | 19 | object SettingsFile { |
| 23 | const val FILE_NAME_CONFIG = "config" | 20 | const val FILE_NAME_CONFIG = "config" |
| 24 | 21 | ||
| 25 | private var sectionsMap = BiMap<String?, String?>() | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves | ||
| 29 | * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it | ||
| 30 | * failed. | ||
| 31 | * | ||
| 32 | * @param ini The ini file to load the settings from | ||
| 33 | * @param isCustomGame | ||
| 34 | * @param view The current view. | ||
| 35 | * @return An Observable that emits a HashMap of the file's contents, then completes. | ||
| 36 | */ | ||
| 37 | private fun readFile( | ||
| 38 | ini: File?, | ||
| 39 | isCustomGame: Boolean, | ||
| 40 | view: SettingsActivityView? = null | ||
| 41 | ): HashMap<String, SettingSection?> { | ||
| 42 | val sections: HashMap<String, SettingSection?> = SettingsSectionMap() | ||
| 43 | var reader: BufferedReader? = null | ||
| 44 | try { | ||
| 45 | reader = BufferedReader(FileReader(ini)) | ||
| 46 | var current: SettingSection? = null | ||
| 47 | var line: String? | ||
| 48 | while (reader.readLine().also { line = it } != null) { | ||
| 49 | if (line!!.startsWith("[") && line!!.endsWith("]")) { | ||
| 50 | current = sectionFromLine(line!!, isCustomGame) | ||
| 51 | sections[current.name] = current | ||
| 52 | } else if (current != null) { | ||
| 53 | val setting = settingFromLine(line!!) | ||
| 54 | if (setting != null) { | ||
| 55 | current.putSetting(setting) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } catch (e: FileNotFoundException) { | ||
| 60 | Log.error("[SettingsFile] File not found: " + e.message) | ||
| 61 | view?.onSettingsFileNotFound() | ||
| 62 | } catch (e: IOException) { | ||
| 63 | Log.error("[SettingsFile] Error reading from: " + e.message) | ||
| 64 | view?.onSettingsFileNotFound() | ||
| 65 | } finally { | ||
| 66 | if (reader != null) { | ||
| 67 | try { | ||
| 68 | reader.close() | ||
| 69 | } catch (e: IOException) { | ||
| 70 | Log.error("[SettingsFile] Error closing: " + e.message) | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | return sections | ||
| 75 | } | ||
| 76 | |||
| 77 | fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> { | ||
| 78 | return readFile(getSettingsFile(fileName), false, view) | ||
| 79 | } | ||
| 80 | |||
| 81 | fun readFile(fileName: String): HashMap<String, SettingSection?> = | ||
| 82 | readFile(getSettingsFile(fileName), false) | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves | ||
| 86 | * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it | ||
| 87 | * failed. | ||
| 88 | * | ||
| 89 | * @param gameId the id of the game to load it's settings. | ||
| 90 | * @param view The current view. | ||
| 91 | */ | ||
| 92 | fun readCustomGameSettings( | ||
| 93 | gameId: String, | ||
| 94 | view: SettingsActivityView? | ||
| 95 | ): HashMap<String, SettingSection?> { | ||
| 96 | return readFile(getCustomGameSettingsFile(gameId), true, view) | ||
| 97 | } | ||
| 98 | |||
| 99 | /** | 22 | /** |
| 100 | * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error | 23 | * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error |
| 101 | * telling why it failed. | 24 | * telling why it failed. |
| 102 | * | 25 | * |
| 103 | * @param fileName The target filename without a path or extension. | 26 | * @param fileName The target filename without a path or extension. |
| 104 | * @param sections The HashMap containing the Settings we want to serialize. | ||
| 105 | * @param view The current view. | ||
| 106 | */ | 27 | */ |
| 107 | fun saveFile( | 28 | fun saveFile(fileName: String) { |
| 108 | fileName: String, | ||
| 109 | sections: TreeMap<String, SettingSection>, | ||
| 110 | view: SettingsActivityView | ||
| 111 | ) { | ||
| 112 | val ini = getSettingsFile(fileName) | 29 | val ini = getSettingsFile(fileName) |
| 113 | try { | 30 | try { |
| 114 | val writer = Wini(ini) | 31 | val wini = Wini(ini) |
| 115 | val keySet: Set<String> = sections.keys | 32 | for (specificCategory in Settings.Category.values()) { |
| 116 | for (key in keySet) { | 33 | val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal) |
| 117 | val section = sections[key] | 34 | for (setting in Settings.settingsList) { |
| 118 | writeSection(writer, section!!) | 35 | if (setting.key!!.isEmpty()) continue |
| 36 | |||
| 37 | val settingCategoryHeader = | ||
| 38 | NativeConfig.getConfigHeader(setting.category.ordinal) | ||
| 39 | val iniSetting: String? = wini.get(categoryHeader, setting.key) | ||
| 40 | if (iniSetting != null || settingCategoryHeader == categoryHeader) { | ||
| 41 | wini.put(settingCategoryHeader, setting.key, setting.valueAsString) | ||
| 42 | } | ||
| 43 | } | ||
| 119 | } | 44 | } |
| 120 | writer.store() | 45 | wini.store() |
| 121 | } catch (e: IOException) { | 46 | } catch (e: IOException) { |
| 122 | Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) | 47 | Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) |
| 123 | view.showToastMessage( | 48 | val context = YuzuApplication.appContext |
| 124 | YuzuApplication.appContext | 49 | Toast.makeText( |
| 125 | .getString(R.string.error_saving, fileName, e.message), | 50 | context, |
| 126 | false | 51 | context.getString(R.string.error_saving, fileName, e.message), |
| 127 | ) | 52 | Toast.LENGTH_SHORT |
| 128 | } | 53 | ).show() |
| 129 | } | ||
| 130 | |||
| 131 | fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) { | ||
| 132 | val sortedSections: Set<String> = TreeSet(sections.keys) | ||
| 133 | for (sectionKey in sortedSections) { | ||
| 134 | val section = sections[sectionKey] | ||
| 135 | val settings = section!!.settings | ||
| 136 | val sortedKeySet: Set<String> = TreeSet(settings.keys) | ||
| 137 | for (settingKey in sortedKeySet) { | ||
| 138 | val setting = settings[settingKey] | ||
| 139 | NativeLibrary.setUserSetting( | ||
| 140 | gameId, | ||
| 141 | mapSectionNameFromIni( | ||
| 142 | section.name | ||
| 143 | ), | ||
| 144 | setting!!.key, | ||
| 145 | setting.valueAsString | ||
| 146 | ) | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | private fun mapSectionNameFromIni(generalSectionName: String): String? { | ||
| 152 | return if (sectionsMap.getForward(generalSectionName) != null) { | ||
| 153 | sectionsMap.getForward(generalSectionName) | ||
| 154 | } else { | ||
| 155 | generalSectionName | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | private fun mapSectionNameToIni(generalSectionName: String): String { | ||
| 160 | return if (sectionsMap.getBackward(generalSectionName) != null) { | ||
| 161 | sectionsMap.getBackward(generalSectionName).toString() | ||
| 162 | } else { | ||
| 163 | generalSectionName | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | fun getSettingsFile(fileName: String): File { | ||
| 168 | return File( | ||
| 169 | DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini" | ||
| 170 | ) | ||
| 171 | } | ||
| 172 | |||
| 173 | private fun getCustomGameSettingsFile(gameId: String): File { | ||
| 174 | return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini") | ||
| 175 | } | ||
| 176 | |||
| 177 | private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection { | ||
| 178 | var sectionName: String = line.substring(1, line.length - 1) | ||
| 179 | if (isCustomGame) { | ||
| 180 | sectionName = mapSectionNameToIni(sectionName) | ||
| 181 | } | 54 | } |
| 182 | return SettingSection(sectionName) | ||
| 183 | } | 55 | } |
| 184 | 56 | ||
| 185 | /** | 57 | fun getSettingsFile(fileName: String): File = |
| 186 | * For a line of text, determines what type of data is being represented, and returns | 58 | File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini") |
| 187 | * a Setting object containing this data. | ||
| 188 | * | ||
| 189 | * @param line The line of text being parsed. | ||
| 190 | * @return A typed Setting containing the key/value contained in the line. | ||
| 191 | */ | ||
| 192 | private fun settingFromLine(line: String): AbstractSetting? { | ||
| 193 | val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() | ||
| 194 | if (splitLine.size != 2) { | ||
| 195 | return null | ||
| 196 | } | ||
| 197 | val key = splitLine[0].trim { it <= ' ' } | ||
| 198 | val value = splitLine[1].trim { it <= ' ' } | ||
| 199 | if (value.isEmpty()) { | ||
| 200 | return null | ||
| 201 | } | ||
| 202 | |||
| 203 | val booleanSetting = BooleanSetting.from(key) | ||
| 204 | if (booleanSetting != null) { | ||
| 205 | booleanSetting.boolean = value.toBoolean() | ||
| 206 | return booleanSetting | ||
| 207 | } | ||
| 208 | |||
| 209 | val intSetting = IntSetting.from(key) | ||
| 210 | if (intSetting != null) { | ||
| 211 | intSetting.int = value.toInt() | ||
| 212 | return intSetting | ||
| 213 | } | ||
| 214 | |||
| 215 | val floatSetting = FloatSetting.from(key) | ||
| 216 | if (floatSetting != null) { | ||
| 217 | floatSetting.float = value.toFloat() | ||
| 218 | return floatSetting | ||
| 219 | } | ||
| 220 | |||
| 221 | val stringSetting = StringSetting.from(key) | ||
| 222 | if (stringSetting != null) { | ||
| 223 | stringSetting.string = value | ||
| 224 | return stringSetting | ||
| 225 | } | ||
| 226 | |||
| 227 | return null | ||
| 228 | } | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Writes the contents of a Section HashMap to disk. | ||
| 232 | * | ||
| 233 | * @param parser A Wini pointed at a file on disk. | ||
| 234 | * @param section A section containing settings to be written to the file. | ||
| 235 | */ | ||
| 236 | private fun writeSection(parser: Wini, section: SettingSection) { | ||
| 237 | // Write the section header. | ||
| 238 | val header = section.name | ||
| 239 | |||
| 240 | // Write this section's values. | ||
| 241 | val settings = section.settings | ||
| 242 | val keySet: Set<String> = settings.keys | ||
| 243 | for (key in keySet) { | ||
| 244 | val setting = settings[key] | ||
| 245 | parser.put(header, setting!!.key, setting.valueAsString) | ||
| 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 | } | ||
| 263 | } | ||
| 264 | } | 59 | } |
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 0e7c1ba88..3e6c157c7 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 | |||
| @@ -7,11 +7,11 @@ import android.annotation.SuppressLint | |||
| 7 | import android.app.AlertDialog | 7 | import android.app.AlertDialog |
| 8 | import android.content.Context | 8 | import android.content.Context |
| 9 | import android.content.DialogInterface | 9 | import android.content.DialogInterface |
| 10 | import android.content.Intent | ||
| 11 | import android.content.SharedPreferences | 10 | import android.content.SharedPreferences |
| 12 | import android.content.pm.ActivityInfo | 11 | import android.content.pm.ActivityInfo |
| 13 | import android.content.res.Configuration | 12 | import android.content.res.Configuration |
| 14 | import android.graphics.Color | 13 | import android.graphics.Color |
| 14 | import android.net.Uri | ||
| 15 | import android.os.Bundle | 15 | import android.os.Bundle |
| 16 | import android.os.Handler | 16 | import android.os.Handler |
| 17 | import android.os.Looper | 17 | import android.os.Looper |
| @@ -19,18 +19,18 @@ import android.util.Rational | |||
| 19 | import android.view.* | 19 | import android.view.* |
| 20 | import android.widget.TextView | 20 | import android.widget.TextView |
| 21 | import androidx.activity.OnBackPressedCallback | 21 | import androidx.activity.OnBackPressedCallback |
| 22 | import androidx.activity.result.ActivityResultLauncher | ||
| 23 | import androidx.activity.result.contract.ActivityResultContracts | ||
| 24 | import androidx.appcompat.widget.PopupMenu | 22 | import androidx.appcompat.widget.PopupMenu |
| 25 | import androidx.core.content.res.ResourcesCompat | 23 | import androidx.core.content.res.ResourcesCompat |
| 26 | import androidx.core.graphics.Insets | 24 | import androidx.core.graphics.Insets |
| 27 | import androidx.core.view.ViewCompat | 25 | import androidx.core.view.ViewCompat |
| 28 | import androidx.core.view.WindowInsetsCompat | 26 | import androidx.core.view.WindowInsetsCompat |
| 29 | import androidx.core.view.isVisible | 27 | import androidx.drawerlayout.widget.DrawerLayout |
| 30 | import androidx.fragment.app.Fragment | 28 | import androidx.fragment.app.Fragment |
| 29 | import androidx.fragment.app.activityViewModels | ||
| 31 | import androidx.lifecycle.Lifecycle | 30 | import androidx.lifecycle.Lifecycle |
| 32 | import androidx.lifecycle.lifecycleScope | 31 | import androidx.lifecycle.lifecycleScope |
| 33 | import androidx.lifecycle.repeatOnLifecycle | 32 | import androidx.lifecycle.repeatOnLifecycle |
| 33 | import androidx.navigation.findNavController | ||
| 34 | import androidx.navigation.fragment.navArgs | 34 | import androidx.navigation.fragment.navArgs |
| 35 | import androidx.preference.PreferenceManager | 35 | import androidx.preference.PreferenceManager |
| 36 | import androidx.window.layout.FoldingFeature | 36 | import androidx.window.layout.FoldingFeature |
| @@ -39,7 +39,9 @@ import androidx.window.layout.WindowLayoutInfo | |||
| 39 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 39 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 40 | import com.google.android.material.slider.Slider | 40 | import com.google.android.material.slider.Slider |
| 41 | import kotlinx.coroutines.Dispatchers | 41 | import kotlinx.coroutines.Dispatchers |
| 42 | import kotlinx.coroutines.flow.collectLatest | ||
| 42 | import kotlinx.coroutines.launch | 43 | import kotlinx.coroutines.launch |
| 44 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 43 | import org.yuzu.yuzu_emu.NativeLibrary | 45 | import org.yuzu.yuzu_emu.NativeLibrary |
| 44 | import org.yuzu.yuzu_emu.R | 46 | import org.yuzu.yuzu_emu.R |
| 45 | import org.yuzu.yuzu_emu.YuzuApplication | 47 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -48,8 +50,8 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding | |||
| 48 | import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding | 50 | import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding |
| 49 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 51 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 50 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 52 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 51 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | 53 | import org.yuzu.yuzu_emu.model.Game |
| 52 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 54 | import org.yuzu.yuzu_emu.model.EmulationViewModel |
| 53 | import org.yuzu.yuzu_emu.overlay.InputOverlay | 55 | import org.yuzu.yuzu_emu.overlay.InputOverlay |
| 54 | import org.yuzu.yuzu_emu.utils.* | 56 | import org.yuzu.yuzu_emu.utils.* |
| 55 | 57 | ||
| @@ -62,11 +64,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 62 | private var _binding: FragmentEmulationBinding? = null | 64 | private var _binding: FragmentEmulationBinding? = null |
| 63 | private val binding get() = _binding!! | 65 | private val binding get() = _binding!! |
| 64 | 66 | ||
| 65 | val args by navArgs<EmulationFragmentArgs>() | 67 | private val args by navArgs<EmulationFragmentArgs>() |
| 66 | 68 | ||
| 67 | private var isInFoldableLayout = false | 69 | private lateinit var game: Game |
| 70 | |||
| 71 | private val emulationViewModel: EmulationViewModel by activityViewModels() | ||
| 68 | 72 | ||
| 69 | private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> | 73 | private var isInFoldableLayout = false |
| 70 | 74 | ||
| 71 | override fun onAttach(context: Context) { | 75 | override fun onAttach(context: Context) { |
| 72 | super.onAttach(context) | 76 | super.onAttach(context) |
| @@ -81,11 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 81 | .collect { updateFoldableLayout(context, it) } | 85 | .collect { updateFoldableLayout(context, it) } |
| 82 | } | 86 | } |
| 83 | } | 87 | } |
| 84 | |||
| 85 | onReturnFromSettings = context.activityResultRegistry.register( | ||
| 86 | "SettingsResult", | ||
| 87 | ActivityResultContracts.StartActivityForResult() | ||
| 88 | ) { updateScreenLayout() } | ||
| 89 | } else { | 88 | } else { |
| 90 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 89 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
| 91 | } | 90 | } |
| @@ -97,10 +96,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 97 | override fun onCreate(savedInstanceState: Bundle?) { | 96 | override fun onCreate(savedInstanceState: Bundle?) { |
| 98 | super.onCreate(savedInstanceState) | 97 | super.onCreate(savedInstanceState) |
| 99 | 98 | ||
| 99 | val intentUri: Uri? = requireActivity().intent.data | ||
| 100 | var intentGame: Game? = null | ||
| 101 | if (intentUri != null) { | ||
| 102 | intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) { | ||
| 103 | GameHelper.getGame(requireActivity().intent.data!!, false) | ||
| 104 | } else { | ||
| 105 | null | ||
| 106 | } | ||
| 107 | } | ||
| 108 | game = if (args.game != null) { | ||
| 109 | args.game!! | ||
| 110 | } else { | ||
| 111 | intentGame ?: error("[EmulationFragment] No bootable game present!") | ||
| 112 | } | ||
| 113 | |||
| 100 | // So this fragment doesn't restart on configuration changes; i.e. rotation. | 114 | // So this fragment doesn't restart on configuration changes; i.e. rotation. |
| 101 | retainInstance = true | 115 | retainInstance = true |
| 102 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 116 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 103 | emulationState = EmulationState(args.game.path) | 117 | emulationState = EmulationState(game.path) |
| 104 | } | 118 | } |
| 105 | 119 | ||
| 106 | /** | 120 | /** |
| @@ -115,16 +129,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 115 | return binding.root | 129 | return binding.root |
| 116 | } | 130 | } |
| 117 | 131 | ||
| 132 | // This is using the correct scope, lint is just acting up | ||
| 133 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
| 118 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 134 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 119 | binding.surfaceEmulation.holder.addCallback(this) | 135 | binding.surfaceEmulation.holder.addCallback(this) |
| 120 | binding.showFpsText.setTextColor(Color.YELLOW) | 136 | binding.showFpsText.setTextColor(Color.YELLOW) |
| 121 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } | 137 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } |
| 122 | 138 | ||
| 123 | // Setup overlay. | 139 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) |
| 124 | updateShowFpsOverlay() | ||
| 125 | |||
| 126 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = | 140 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = |
| 127 | args.game.title | 141 | game.title |
| 128 | binding.inGameMenu.setNavigationItemSelectedListener { | 142 | binding.inGameMenu.setNavigationItemSelectedListener { |
| 129 | when (it.itemId) { | 143 | when (it.itemId) { |
| 130 | R.id.menu_pause_emulation -> { | 144 | R.id.menu_pause_emulation -> { |
| @@ -149,12 +163,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 149 | } | 163 | } |
| 150 | 164 | ||
| 151 | R.id.menu_settings -> { | 165 | R.id.menu_settings -> { |
| 152 | SettingsActivity.launch( | 166 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
| 153 | requireContext(), | 167 | null, |
| 154 | onReturnFromSettings, | 168 | Settings.MenuTag.SECTION_ROOT |
| 155 | SettingsFile.FILE_NAME_CONFIG, | ||
| 156 | "" | ||
| 157 | ) | 169 | ) |
| 170 | binding.root.findNavController().navigate(action) | ||
| 158 | true | 171 | true |
| 159 | } | 172 | } |
| 160 | 173 | ||
| @@ -165,7 +178,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 165 | 178 | ||
| 166 | R.id.menu_exit -> { | 179 | R.id.menu_exit -> { |
| 167 | emulationState.stop() | 180 | emulationState.stop() |
| 168 | requireActivity().finish() | 181 | emulationViewModel.setIsEmulationStopping(true) |
| 182 | binding.drawerLayout.close() | ||
| 183 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | ||
| 169 | true | 184 | true |
| 170 | } | 185 | } |
| 171 | 186 | ||
| @@ -179,6 +194,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 179 | requireActivity(), | 194 | requireActivity(), |
| 180 | object : OnBackPressedCallback(true) { | 195 | object : OnBackPressedCallback(true) { |
| 181 | override fun handleOnBackPressed() { | 196 | override fun handleOnBackPressed() { |
| 197 | if (!NativeLibrary.isRunning()) { | ||
| 198 | return | ||
| 199 | } | ||
| 200 | |||
| 182 | if (binding.drawerLayout.isOpen) { | 201 | if (binding.drawerLayout.isOpen) { |
| 183 | binding.drawerLayout.close() | 202 | binding.drawerLayout.close() |
| 184 | } else { | 203 | } else { |
| @@ -188,11 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 188 | } | 207 | } |
| 189 | ) | 208 | ) |
| 190 | 209 | ||
| 191 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { | 210 | GameIconUtils.loadGameIcon(game, binding.loadingImage) |
| 192 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | 211 | binding.loadingTitle.text = game.title |
| 193 | WindowInfoTracker.getOrCreate(requireContext()) | 212 | binding.loadingTitle.isSelected = true |
| 194 | .windowLayoutInfo(requireActivity()) | 213 | binding.loadingText.isSelected = true |
| 195 | .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } | 214 | |
| 215 | viewLifecycleOwner.lifecycleScope.apply { | ||
| 216 | launch { | ||
| 217 | repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 218 | WindowInfoTracker.getOrCreate(requireContext()) | ||
| 219 | .windowLayoutInfo(requireActivity()) | ||
| 220 | .collect { | ||
| 221 | updateFoldableLayout(requireActivity() as EmulationActivity, it) | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | launch { | ||
| 226 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 227 | emulationViewModel.shaderProgress.collectLatest { | ||
| 228 | if (it > 0 && it != emulationViewModel.totalShaders.value) { | ||
| 229 | binding.loadingProgressIndicator.isIndeterminate = false | ||
| 230 | |||
| 231 | if (it < binding.loadingProgressIndicator.max) { | ||
| 232 | binding.loadingProgressIndicator.progress = it | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | if (it == emulationViewModel.totalShaders.value) { | ||
| 237 | binding.loadingText.setText(R.string.loading) | ||
| 238 | binding.loadingProgressIndicator.isIndeterminate = true | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | launch { | ||
| 244 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 245 | emulationViewModel.totalShaders.collectLatest { | ||
| 246 | binding.loadingProgressIndicator.max = it | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | launch { | ||
| 251 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 252 | emulationViewModel.shaderMessage.collectLatest { | ||
| 253 | if (it.isNotEmpty()) { | ||
| 254 | binding.loadingText.text = it | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | } | ||
| 259 | launch { | ||
| 260 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 261 | emulationViewModel.emulationStarted.collectLatest { | ||
| 262 | if (it) { | ||
| 263 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) | ||
| 264 | ViewUtils.showView(binding.surfaceInputOverlay) | ||
| 265 | ViewUtils.hideView(binding.loadingIndicator) | ||
| 266 | |||
| 267 | // Setup overlay | ||
| 268 | updateShowFpsOverlay() | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | launch { | ||
| 274 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 275 | emulationViewModel.isEmulationStopping.collectLatest { | ||
| 276 | if (it) { | ||
| 277 | binding.loadingText.setText(R.string.shutting_down) | ||
| 278 | ViewUtils.showView(binding.loadingIndicator) | ||
| 279 | ViewUtils.hideView(binding.inputContainer) | ||
| 280 | ViewUtils.hideView(binding.showFpsText) | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 196 | } | 284 | } |
| 197 | } | 285 | } |
| 198 | } | 286 | } |
| @@ -204,11 +292,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 204 | binding.drawerLayout.close() | 292 | binding.drawerLayout.close() |
| 205 | } | 293 | } |
| 206 | if (EmulationMenuSettings.showOverlay) { | 294 | if (EmulationMenuSettings.showOverlay) { |
| 207 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } | 295 | binding.surfaceInputOverlay.post { |
| 296 | binding.surfaceInputOverlay.visibility = View.VISIBLE | ||
| 297 | } | ||
| 208 | } | 298 | } |
| 209 | } else { | 299 | } else { |
| 210 | if (EmulationMenuSettings.showOverlay) { | 300 | if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { |
| 211 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } | 301 | binding.surfaceInputOverlay.post { |
| 302 | binding.surfaceInputOverlay.visibility = View.VISIBLE | ||
| 303 | } | ||
| 304 | } else { | ||
| 305 | binding.surfaceInputOverlay.post { | ||
| 306 | binding.surfaceInputOverlay.visibility = View.INVISIBLE | ||
| 307 | } | ||
| 212 | } | 308 | } |
| 213 | if (!isInFoldableLayout) { | 309 | if (!isInFoldableLayout) { |
| 214 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | 310 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { |
| @@ -217,16 +313,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 217 | binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE | 313 | binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE |
| 218 | } | 314 | } |
| 219 | } | 315 | } |
| 220 | if (!binding.surfaceInputOverlay.isInEditMode) { | ||
| 221 | refreshInputOverlay() | ||
| 222 | } | ||
| 223 | } | 316 | } |
| 224 | } | 317 | } |
| 225 | 318 | ||
| 226 | override fun onResume() { | 319 | override fun onResume() { |
| 227 | super.onResume() | 320 | super.onResume() |
| 228 | if (!DirectoryInitialization.areDirectoriesReady) { | 321 | if (!DirectoryInitialization.areDirectoriesReady) { |
| 229 | DirectoryInitialization.start(requireContext()) | 322 | DirectoryInitialization.start() |
| 230 | } | 323 | } |
| 231 | 324 | ||
| 232 | updateScreenLayout() | 325 | updateScreenLayout() |
| @@ -251,10 +344,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 251 | super.onDetach() | 344 | super.onDetach() |
| 252 | } | 345 | } |
| 253 | 346 | ||
| 254 | private fun refreshInputOverlay() { | ||
| 255 | binding.surfaceInputOverlay.refreshControls() | ||
| 256 | } | ||
| 257 | |||
| 258 | private fun resetInputOverlay() { | 347 | private fun resetInputOverlay() { |
| 259 | preferences.edit() | 348 | preferences.edit() |
| 260 | .remove(Settings.PREF_CONTROL_SCALE) | 349 | .remove(Settings.PREF_CONTROL_SCALE) |
| @@ -272,17 +361,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 272 | val FRAMETIME = 2 | 361 | val FRAMETIME = 2 |
| 273 | val SPEED = 3 | 362 | val SPEED = 3 |
| 274 | perfStatsUpdater = { | 363 | perfStatsUpdater = { |
| 275 | val perfStats = NativeLibrary.getPerfStats() | 364 | if (emulationViewModel.emulationStarted.value == true) { |
| 276 | if (perfStats[FPS] > 0 && _binding != null) { | 365 | val perfStats = NativeLibrary.getPerfStats() |
| 277 | binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS]) | 366 | if (perfStats[FPS] > 0 && _binding != null) { |
| 278 | } | 367 | binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS]) |
| 279 | 368 | } | |
| 280 | if (!emulationState.isStopped) { | ||
| 281 | perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100) | 369 | perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100) |
| 282 | } | 370 | } |
| 283 | } | 371 | } |
| 284 | perfStatsUpdateHandler.post(perfStatsUpdater!!) | 372 | perfStatsUpdateHandler.post(perfStatsUpdater!!) |
| 285 | binding.showFpsText.text = resources.getString(R.string.emulation_game_loading) | ||
| 286 | binding.showFpsText.visibility = View.VISIBLE | 373 | binding.showFpsText.visibility = View.VISIBLE |
| 287 | } else { | 374 | } else { |
| 288 | if (perfStatsUpdater != null) { | 375 | if (perfStatsUpdater != null) { |
| @@ -297,11 +384,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 297 | emulationActivity?.let { | 384 | emulationActivity?.let { |
| 298 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { | 385 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { |
| 299 | Settings.LayoutOption_MobileLandscape -> | 386 | Settings.LayoutOption_MobileLandscape -> |
| 300 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | 387 | ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE |
| 301 | Settings.LayoutOption_MobilePortrait -> | 388 | Settings.LayoutOption_MobilePortrait -> |
| 302 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | 389 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT |
| 303 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 390 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
| 304 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | 391 | else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE |
| 305 | } | 392 | } |
| 306 | } | 393 | } |
| 307 | } | 394 | } |
| @@ -340,7 +427,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 340 | 427 | ||
| 341 | isInFoldableLayout = true | 428 | isInFoldableLayout = true |
| 342 | binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE | 429 | binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE |
| 343 | refreshInputOverlay() | ||
| 344 | } | 430 | } |
| 345 | } | 431 | } |
| 346 | it.isSeparating | 432 | it.isSeparating |
| @@ -428,7 +514,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 428 | .apply() | 514 | .apply() |
| 429 | } | 515 | } |
| 430 | .setPositiveButton(android.R.string.ok) { _, _ -> | 516 | .setPositiveButton(android.R.string.ok) { _, _ -> |
| 431 | refreshInputOverlay() | 517 | binding.surfaceInputOverlay.refreshControls() |
| 432 | } | 518 | } |
| 433 | .setNegativeButton(android.R.string.cancel, null) | 519 | .setNegativeButton(android.R.string.cancel, null) |
| 434 | .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> } | 520 | .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> } |
| @@ -452,7 +538,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 452 | R.id.menu_show_overlay -> { | 538 | R.id.menu_show_overlay -> { |
| 453 | it.isChecked = !it.isChecked | 539 | it.isChecked = !it.isChecked |
| 454 | EmulationMenuSettings.showOverlay = it.isChecked | 540 | EmulationMenuSettings.showOverlay = it.isChecked |
| 455 | refreshInputOverlay() | 541 | binding.surfaceInputOverlay.refreshControls() |
| 456 | true | 542 | true |
| 457 | } | 543 | } |
| 458 | 544 | ||
| @@ -558,14 +644,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 558 | preferences.edit() | 644 | preferences.edit() |
| 559 | .putInt(Settings.PREF_CONTROL_SCALE, scale) | 645 | .putInt(Settings.PREF_CONTROL_SCALE, scale) |
| 560 | .apply() | 646 | .apply() |
| 561 | refreshInputOverlay() | 647 | binding.surfaceInputOverlay.refreshControls() |
| 562 | } | 648 | } |
| 563 | 649 | ||
| 564 | private fun setControlOpacity(opacity: Int) { | 650 | private fun setControlOpacity(opacity: Int) { |
| 565 | preferences.edit() | 651 | preferences.edit() |
| 566 | .putInt(Settings.PREF_CONTROL_OPACITY, opacity) | 652 | .putInt(Settings.PREF_CONTROL_OPACITY, opacity) |
| 567 | .apply() | 653 | .apply() |
| 568 | refreshInputOverlay() | 654 | binding.surfaceInputOverlay.refreshControls() |
| 569 | } | 655 | } |
| 570 | 656 | ||
| 571 | private fun setInsets() { | 657 | private fun setInsets() { |
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 c001af892..c119e69c9 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 | |||
| @@ -25,18 +25,18 @@ import androidx.core.view.updatePadding | |||
| 25 | import androidx.documentfile.provider.DocumentFile | 25 | import androidx.documentfile.provider.DocumentFile |
| 26 | import androidx.fragment.app.Fragment | 26 | import androidx.fragment.app.Fragment |
| 27 | import androidx.fragment.app.activityViewModels | 27 | import androidx.fragment.app.activityViewModels |
| 28 | import androidx.navigation.findNavController | ||
| 28 | import androidx.navigation.fragment.findNavController | 29 | import androidx.navigation.fragment.findNavController |
| 29 | import androidx.recyclerview.widget.LinearLayoutManager | 30 | import androidx.recyclerview.widget.LinearLayoutManager |
| 30 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 31 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 31 | import com.google.android.material.transition.MaterialSharedAxis | 32 | import com.google.android.material.transition.MaterialSharedAxis |
| 32 | import org.yuzu.yuzu_emu.BuildConfig | 33 | import org.yuzu.yuzu_emu.BuildConfig |
| 34 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 33 | import org.yuzu.yuzu_emu.R | 35 | import org.yuzu.yuzu_emu.R |
| 34 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter | 36 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter |
| 35 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding | 37 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding |
| 36 | import org.yuzu.yuzu_emu.features.DocumentProvider | 38 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 37 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 39 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 38 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | ||
| 39 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | ||
| 40 | import org.yuzu.yuzu_emu.model.HomeSetting | 40 | import org.yuzu.yuzu_emu.model.HomeSetting |
| 41 | import org.yuzu.yuzu_emu.model.HomeViewModel | 41 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 42 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 42 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| @@ -74,7 +74,13 @@ class HomeSettingsFragment : Fragment() { | |||
| 74 | R.string.advanced_settings, | 74 | R.string.advanced_settings, |
| 75 | R.string.settings_description, | 75 | R.string.settings_description, |
| 76 | R.drawable.ic_settings, | 76 | R.drawable.ic_settings, |
| 77 | { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } | 77 | { |
| 78 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( | ||
| 79 | null, | ||
| 80 | Settings.MenuTag.SECTION_ROOT | ||
| 81 | ) | ||
| 82 | binding.root.findNavController().navigate(action) | ||
| 83 | } | ||
| 78 | ) | 84 | ) |
| 79 | ) | 85 | ) |
| 80 | add( | 86 | add( |
| @@ -90,7 +96,13 @@ class HomeSettingsFragment : Fragment() { | |||
| 90 | R.string.preferences_theme, | 96 | R.string.preferences_theme, |
| 91 | R.string.theme_and_color_description, | 97 | R.string.theme_and_color_description, |
| 92 | R.drawable.ic_palette, | 98 | R.drawable.ic_palette, |
| 93 | { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } | 99 | { |
| 100 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( | ||
| 101 | null, | ||
| 102 | Settings.MenuTag.SECTION_THEME | ||
| 103 | ) | ||
| 104 | binding.root.findNavController().navigate(action) | ||
| 105 | } | ||
| 94 | ) | 106 | ) |
| 95 | ) | 107 | ) |
| 96 | add( | 108 | add( |
| @@ -129,7 +141,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 129 | mainActivity.getGamesDirectory.launch( | 141 | mainActivity.getGamesDirectory.launch( |
| 130 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | 142 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data |
| 131 | ) | 143 | ) |
| 132 | } | 144 | }, |
| 145 | { true }, | ||
| 146 | 0, | ||
| 147 | 0, | ||
| 148 | homeViewModel.gamesDir | ||
| 133 | ) | 149 | ) |
| 134 | ) | 150 | ) |
| 135 | add( | 151 | add( |
| @@ -201,7 +217,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 201 | 217 | ||
| 202 | binding.homeSettingsList.apply { | 218 | binding.homeSettingsList.apply { |
| 203 | layoutManager = LinearLayoutManager(requireContext()) | 219 | layoutManager = LinearLayoutManager(requireContext()) |
| 204 | adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList) | 220 | adapter = HomeSettingAdapter( |
| 221 | requireActivity() as AppCompatActivity, | ||
| 222 | viewLifecycleOwner, | ||
| 223 | optionsList | ||
| 224 | ) | ||
| 205 | } | 225 | } |
| 206 | 226 | ||
| 207 | setInsets() | 227 | setInsets() |
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 e1495ee8c..f38aeea53 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 | |||
| @@ -187,8 +187,8 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 187 | withContext(Dispatchers.Main) { | 187 | withContext(Dispatchers.Main) { |
| 188 | if (!validZip) { | 188 | if (!validZip) { |
| 189 | MessageDialogFragment.newInstance( | 189 | MessageDialogFragment.newInstance( |
| 190 | R.string.save_file_invalid_zip_structure, | 190 | titleId = R.string.save_file_invalid_zip_structure, |
| 191 | R.string.save_file_invalid_zip_structure_description | 191 | descriptionId = R.string.save_file_invalid_zip_structure_description |
| 192 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | 192 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
| 193 | return@withContext | 193 | return@withContext |
| 194 | } | 194 | } |
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 739b26f99..18bc34b9f 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 | |||
| @@ -5,49 +5,75 @@ package org.yuzu.yuzu_emu.fragments | |||
| 5 | 5 | ||
| 6 | import android.app.Dialog | 6 | import android.app.Dialog |
| 7 | import android.os.Bundle | 7 | import android.os.Bundle |
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 8 | import android.widget.Toast | 11 | import android.widget.Toast |
| 9 | import androidx.appcompat.app.AppCompatActivity | 12 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.fragment.app.DialogFragment | 13 | import androidx.fragment.app.DialogFragment |
| 11 | import androidx.fragment.app.activityViewModels | 14 | import androidx.fragment.app.activityViewModels |
| 15 | import androidx.lifecycle.Lifecycle | ||
| 12 | import androidx.lifecycle.ViewModelProvider | 16 | import androidx.lifecycle.ViewModelProvider |
| 17 | import androidx.lifecycle.lifecycleScope | ||
| 18 | import androidx.lifecycle.repeatOnLifecycle | ||
| 13 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 20 | import kotlinx.coroutines.launch | ||
| 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 21 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 15 | import org.yuzu.yuzu_emu.model.TaskViewModel | 22 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 16 | 23 | ||
| 17 | class IndeterminateProgressDialogFragment : DialogFragment() { | 24 | class IndeterminateProgressDialogFragment : DialogFragment() { |
| 18 | private val taskViewModel: TaskViewModel by activityViewModels() | 25 | private val taskViewModel: TaskViewModel by activityViewModels() |
| 19 | 26 | ||
| 27 | private lateinit var binding: DialogProgressBarBinding | ||
| 28 | |||
| 20 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 29 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 21 | val titleId = requireArguments().getInt(TITLE) | 30 | val titleId = requireArguments().getInt(TITLE) |
| 22 | 31 | ||
| 23 | val progressBinding = DialogProgressBarBinding.inflate(layoutInflater) | 32 | binding = DialogProgressBarBinding.inflate(layoutInflater) |
| 24 | progressBinding.progressBar.isIndeterminate = true | 33 | binding.progressBar.isIndeterminate = true |
| 25 | val dialog = MaterialAlertDialogBuilder(requireContext()) | 34 | val dialog = MaterialAlertDialogBuilder(requireContext()) |
| 26 | .setTitle(titleId) | 35 | .setTitle(titleId) |
| 27 | .setView(progressBinding.root) | 36 | .setView(binding.root) |
| 28 | .create() | 37 | .create() |
| 29 | dialog.setCanceledOnTouchOutside(false) | 38 | dialog.setCanceledOnTouchOutside(false) |
| 30 | 39 | ||
| 31 | taskViewModel.isComplete.observe(this) { complete -> | 40 | if (!taskViewModel.isRunning.value) { |
| 32 | if (complete) { | ||
| 33 | dialog.dismiss() | ||
| 34 | when (val result = taskViewModel.result.value) { | ||
| 35 | is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() | ||
| 36 | is MessageDialogFragment -> result.show( | ||
| 37 | parentFragmentManager, | ||
| 38 | MessageDialogFragment.TAG | ||
| 39 | ) | ||
| 40 | } | ||
| 41 | taskViewModel.clear() | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | if (taskViewModel.isRunning.value == false) { | ||
| 46 | taskViewModel.runTask() | 41 | taskViewModel.runTask() |
| 47 | } | 42 | } |
| 48 | return dialog | 43 | return dialog |
| 49 | } | 44 | } |
| 50 | 45 | ||
| 46 | override fun onCreateView( | ||
| 47 | inflater: LayoutInflater, | ||
| 48 | container: ViewGroup?, | ||
| 49 | savedInstanceState: Bundle? | ||
| 50 | ): View { | ||
| 51 | return binding.root | ||
| 52 | } | ||
| 53 | |||
| 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 55 | super.onViewCreated(view, savedInstanceState) | ||
| 56 | viewLifecycleOwner.lifecycleScope.launch { | ||
| 57 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 58 | taskViewModel.isComplete.collect { | ||
| 59 | if (it) { | ||
| 60 | dismiss() | ||
| 61 | when (val result = taskViewModel.result.value) { | ||
| 62 | is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) | ||
| 63 | .show() | ||
| 64 | |||
| 65 | is MessageDialogFragment -> result.show( | ||
| 66 | requireActivity().supportFragmentManager, | ||
| 67 | MessageDialogFragment.TAG | ||
| 68 | ) | ||
| 69 | } | ||
| 70 | taskViewModel.clear() | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 51 | companion object { | 77 | companion object { |
| 52 | const val TAG = "IndeterminateProgressDialogFragment" | 78 | const val TAG = "IndeterminateProgressDialogFragment" |
| 53 | 79 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt deleted file mode 100644 index b29b627e9..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt +++ /dev/null | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.Intent | ||
| 8 | import android.net.Uri | ||
| 9 | import android.os.Bundle | ||
| 10 | import androidx.fragment.app.DialogFragment | ||
| 11 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 12 | import org.yuzu.yuzu_emu.R | ||
| 13 | |||
| 14 | class LongMessageDialogFragment : DialogFragment() { | ||
| 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 16 | val titleId = requireArguments().getInt(TITLE) | ||
| 17 | val description = requireArguments().getString(DESCRIPTION) | ||
| 18 | val helpLinkId = requireArguments().getInt(HELP_LINK) | ||
| 19 | |||
| 20 | val dialog = MaterialAlertDialogBuilder(requireContext()) | ||
| 21 | .setPositiveButton(R.string.close, null) | ||
| 22 | .setTitle(titleId) | ||
| 23 | .setMessage(description) | ||
| 24 | |||
| 25 | if (helpLinkId != 0) { | ||
| 26 | dialog.setNeutralButton(R.string.learn_more) { _, _ -> | ||
| 27 | openLink(getString(helpLinkId)) | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | return dialog.show() | ||
| 32 | } | ||
| 33 | |||
| 34 | private fun openLink(link: String) { | ||
| 35 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) | ||
| 36 | startActivity(intent) | ||
| 37 | } | ||
| 38 | |||
| 39 | companion object { | ||
| 40 | const val TAG = "LongMessageDialogFragment" | ||
| 41 | |||
| 42 | private const val TITLE = "Title" | ||
| 43 | private const val DESCRIPTION = "Description" | ||
| 44 | private const val HELP_LINK = "Link" | ||
| 45 | |||
| 46 | fun newInstance( | ||
| 47 | titleId: Int, | ||
| 48 | description: String, | ||
| 49 | helpLinkId: Int = 0 | ||
| 50 | ): LongMessageDialogFragment { | ||
| 51 | val dialog = LongMessageDialogFragment() | ||
| 52 | val bundle = Bundle() | ||
| 53 | bundle.apply { | ||
| 54 | putInt(TITLE, titleId) | ||
| 55 | putString(DESCRIPTION, description) | ||
| 56 | putInt(HELP_LINK, helpLinkId) | ||
| 57 | } | ||
| 58 | dialog.arguments = bundle | ||
| 59 | return dialog | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index 2db38fdc2..7d1c2c8dd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt | |||
| @@ -13,14 +13,20 @@ import org.yuzu.yuzu_emu.R | |||
| 13 | 13 | ||
| 14 | class MessageDialogFragment : DialogFragment() { | 14 | class MessageDialogFragment : DialogFragment() { |
| 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 16 | val titleId = requireArguments().getInt(TITLE) | 16 | val titleId = requireArguments().getInt(TITLE_ID) |
| 17 | val descriptionId = requireArguments().getInt(DESCRIPTION) | 17 | val titleString = requireArguments().getString(TITLE_STRING)!! |
| 18 | val descriptionId = requireArguments().getInt(DESCRIPTION_ID) | ||
| 19 | val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!! | ||
| 18 | val helpLinkId = requireArguments().getInt(HELP_LINK) | 20 | val helpLinkId = requireArguments().getInt(HELP_LINK) |
| 19 | 21 | ||
| 20 | val dialog = MaterialAlertDialogBuilder(requireContext()) | 22 | val dialog = MaterialAlertDialogBuilder(requireContext()) |
| 21 | .setPositiveButton(R.string.close, null) | 23 | .setPositiveButton(R.string.close, null) |
| 22 | .setTitle(titleId) | 24 | |
| 23 | .setMessage(descriptionId) | 25 | if (titleId != 0) dialog.setTitle(titleId) |
| 26 | if (titleString.isNotEmpty()) dialog.setTitle(titleString) | ||
| 27 | |||
| 28 | if (descriptionId != 0) dialog.setMessage(descriptionId) | ||
| 29 | if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) | ||
| 24 | 30 | ||
| 25 | if (helpLinkId != 0) { | 31 | if (helpLinkId != 0) { |
| 26 | dialog.setNeutralButton(R.string.learn_more) { _, _ -> | 32 | dialog.setNeutralButton(R.string.learn_more) { _, _ -> |
| @@ -39,20 +45,26 @@ class MessageDialogFragment : DialogFragment() { | |||
| 39 | companion object { | 45 | companion object { |
| 40 | const val TAG = "MessageDialogFragment" | 46 | const val TAG = "MessageDialogFragment" |
| 41 | 47 | ||
| 42 | private const val TITLE = "Title" | 48 | private const val TITLE_ID = "Title" |
| 43 | private const val DESCRIPTION = "Description" | 49 | private const val TITLE_STRING = "TitleString" |
| 50 | private const val DESCRIPTION_ID = "DescriptionId" | ||
| 51 | private const val DESCRIPTION_STRING = "DescriptionString" | ||
| 44 | private const val HELP_LINK = "Link" | 52 | private const val HELP_LINK = "Link" |
| 45 | 53 | ||
| 46 | fun newInstance( | 54 | fun newInstance( |
| 47 | titleId: Int, | 55 | titleId: Int = 0, |
| 48 | descriptionId: Int, | 56 | titleString: String = "", |
| 57 | descriptionId: Int = 0, | ||
| 58 | descriptionString: String = "", | ||
| 49 | helpLinkId: Int = 0 | 59 | helpLinkId: Int = 0 |
| 50 | ): MessageDialogFragment { | 60 | ): MessageDialogFragment { |
| 51 | val dialog = MessageDialogFragment() | 61 | val dialog = MessageDialogFragment() |
| 52 | val bundle = Bundle() | 62 | val bundle = Bundle() |
| 53 | bundle.apply { | 63 | bundle.apply { |
| 54 | putInt(TITLE, titleId) | 64 | putInt(TITLE_ID, titleId) |
| 55 | putInt(DESCRIPTION, descriptionId) | 65 | putString(TITLE_STRING, titleString) |
| 66 | putInt(DESCRIPTION_ID, descriptionId) | ||
| 67 | putString(DESCRIPTION_STRING, descriptionString) | ||
| 56 | putInt(HELP_LINK, helpLinkId) | 68 | putInt(HELP_LINK, helpLinkId) |
| 57 | } | 69 | } |
| 58 | dialog.arguments = bundle | 70 | dialog.arguments = bundle |
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 f54dccc69..2dbca76a5 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 | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | ||
| 6 | import android.content.Context | 7 | import android.content.Context |
| 7 | import android.content.SharedPreferences | 8 | import android.content.SharedPreferences |
| 8 | import android.os.Bundle | 9 | import android.os.Bundle |
| @@ -17,9 +18,13 @@ import androidx.core.view.updatePadding | |||
| 17 | import androidx.core.widget.doOnTextChanged | 18 | import androidx.core.widget.doOnTextChanged |
| 18 | import androidx.fragment.app.Fragment | 19 | import androidx.fragment.app.Fragment |
| 19 | import androidx.fragment.app.activityViewModels | 20 | import androidx.fragment.app.activityViewModels |
| 21 | import androidx.lifecycle.Lifecycle | ||
| 22 | import androidx.lifecycle.lifecycleScope | ||
| 23 | import androidx.lifecycle.repeatOnLifecycle | ||
| 20 | import androidx.preference.PreferenceManager | 24 | import androidx.preference.PreferenceManager |
| 21 | import info.debatty.java.stringsimilarity.Jaccard | 25 | import info.debatty.java.stringsimilarity.Jaccard |
| 22 | import info.debatty.java.stringsimilarity.JaroWinkler | 26 | import info.debatty.java.stringsimilarity.JaroWinkler |
| 27 | import kotlinx.coroutines.launch | ||
| 23 | import java.util.Locale | 28 | import java.util.Locale |
| 24 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 25 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -52,6 +57,8 @@ class SearchFragment : Fragment() { | |||
| 52 | return binding.root | 57 | return binding.root |
| 53 | } | 58 | } |
| 54 | 59 | ||
| 60 | // This is using the correct scope, lint is just acting up | ||
| 61 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
| 55 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 56 | homeViewModel.setNavigationVisibility(visible = true, animated = false) | 63 | homeViewModel.setNavigationVisibility(visible = true, animated = false) |
| 57 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 64 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| @@ -79,21 +86,32 @@ class SearchFragment : Fragment() { | |||
| 79 | filterAndSearch() | 86 | filterAndSearch() |
| 80 | } | 87 | } |
| 81 | 88 | ||
| 82 | gamesViewModel.apply { | 89 | viewLifecycleOwner.lifecycleScope.apply { |
| 83 | searchFocused.observe(viewLifecycleOwner) { searchFocused -> | 90 | launch { |
| 84 | if (searchFocused) { | 91 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 85 | focusSearch() | 92 | gamesViewModel.searchFocused.collect { |
| 86 | gamesViewModel.setSearchFocused(false) | 93 | if (it) { |
| 94 | focusSearch() | ||
| 95 | gamesViewModel.setSearchFocused(false) | ||
| 96 | } | ||
| 97 | } | ||
| 87 | } | 98 | } |
| 88 | } | 99 | } |
| 89 | 100 | launch { | |
| 90 | games.observe(viewLifecycleOwner) { filterAndSearch() } | 101 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 91 | searchedGames.observe(viewLifecycleOwner) { | 102 | gamesViewModel.games.collect { filterAndSearch() } |
| 92 | (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) | 103 | } |
| 93 | if (it.isEmpty()) { | 104 | } |
| 94 | binding.noResultsView.visibility = View.VISIBLE | 105 | launch { |
| 95 | } else { | 106 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 96 | binding.noResultsView.visibility = View.GONE | 107 | gamesViewModel.searchedGames.collect { |
| 108 | (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) | ||
| 109 | if (it.isEmpty()) { | ||
| 110 | binding.noResultsView.visibility = View.VISIBLE | ||
| 111 | } else { | ||
| 112 | binding.noResultsView.visibility = View.GONE | ||
| 113 | } | ||
| 114 | } | ||
| 97 | } | 115 | } |
| 98 | } | 116 | } |
| 99 | } | 117 | } |
| @@ -109,7 +127,7 @@ class SearchFragment : Fragment() { | |||
| 109 | private inner class ScoredGame(val score: Double, val item: Game) | 127 | private inner class ScoredGame(val score: Double, val item: Game) |
| 110 | 128 | ||
| 111 | private fun filterAndSearch() { | 129 | private fun filterAndSearch() { |
| 112 | val baseList = gamesViewModel.games.value!! | 130 | val baseList = gamesViewModel.games.value |
| 113 | val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { | 131 | val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { |
| 114 | R.id.chip_recently_played -> { | 132 | R.id.chip_recently_played -> { |
| 115 | baseList.filter { | 133 | baseList.filter { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt new file mode 100644 index 000000000..d18ec6974 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt | |||
| @@ -0,0 +1,235 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.DialogInterface | ||
| 8 | import android.os.Bundle | ||
| 9 | import android.view.LayoutInflater | ||
| 10 | import android.view.View | ||
| 11 | import android.view.ViewGroup | ||
| 12 | import androidx.fragment.app.DialogFragment | ||
| 13 | import androidx.fragment.app.activityViewModels | ||
| 14 | import androidx.lifecycle.Lifecycle | ||
| 15 | import androidx.lifecycle.lifecycleScope | ||
| 16 | import androidx.lifecycle.repeatOnLifecycle | ||
| 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 18 | import com.google.android.material.slider.Slider | ||
| 19 | import kotlinx.coroutines.launch | ||
| 20 | import org.yuzu.yuzu_emu.R | ||
| 21 | import org.yuzu.yuzu_emu.databinding.DialogSliderBinding | ||
| 22 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 23 | import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting | ||
| 24 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting | ||
| 25 | import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting | ||
| 26 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 27 | |||
| 28 | class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener { | ||
| 29 | private var type = 0 | ||
| 30 | private var position = 0 | ||
| 31 | |||
| 32 | private var defaultCancelListener = | ||
| 33 | DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } | ||
| 34 | |||
| 35 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
| 36 | |||
| 37 | private lateinit var sliderBinding: DialogSliderBinding | ||
| 38 | |||
| 39 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 40 | super.onCreate(savedInstanceState) | ||
| 41 | type = requireArguments().getInt(TYPE) | ||
| 42 | position = requireArguments().getInt(POSITION) | ||
| 43 | |||
| 44 | if (settingsViewModel.clickedItem == null) dismiss() | ||
| 45 | } | ||
| 46 | |||
| 47 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 48 | return when (type) { | ||
| 49 | TYPE_RESET_SETTING -> { | ||
| 50 | MaterialAlertDialogBuilder(requireContext()) | ||
| 51 | .setMessage(R.string.reset_setting_confirmation) | ||
| 52 | .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> | ||
| 53 | settingsViewModel.clickedItem!!.setting.reset() | ||
| 54 | settingsViewModel.setAdapterItemChanged(position) | ||
| 55 | settingsViewModel.shouldSave = true | ||
| 56 | } | ||
| 57 | .setNegativeButton(android.R.string.cancel, null) | ||
| 58 | .create() | ||
| 59 | } | ||
| 60 | |||
| 61 | SettingsItem.TYPE_SINGLE_CHOICE -> { | ||
| 62 | val item = settingsViewModel.clickedItem as SingleChoiceSetting | ||
| 63 | val value = getSelectionForSingleChoiceValue(item) | ||
| 64 | MaterialAlertDialogBuilder(requireContext()) | ||
| 65 | .setTitle(item.nameId) | ||
| 66 | .setSingleChoiceItems(item.choicesId, value, this) | ||
| 67 | .create() | ||
| 68 | } | ||
| 69 | |||
| 70 | SettingsItem.TYPE_SLIDER -> { | ||
| 71 | sliderBinding = DialogSliderBinding.inflate(layoutInflater) | ||
| 72 | val item = settingsViewModel.clickedItem as SliderSetting | ||
| 73 | |||
| 74 | settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units) | ||
| 75 | sliderBinding.slider.apply { | ||
| 76 | valueFrom = item.min.toFloat() | ||
| 77 | valueTo = item.max.toFloat() | ||
| 78 | value = settingsViewModel.sliderProgress.value.toFloat() | ||
| 79 | addOnChangeListener { _: Slider, value: Float, _: Boolean -> | ||
| 80 | settingsViewModel.setSliderTextValue(value, item.units) | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | MaterialAlertDialogBuilder(requireContext()) | ||
| 85 | .setTitle(item.nameId) | ||
| 86 | .setView(sliderBinding.root) | ||
| 87 | .setPositiveButton(android.R.string.ok, this) | ||
| 88 | .setNegativeButton(android.R.string.cancel, defaultCancelListener) | ||
| 89 | .create() | ||
| 90 | } | ||
| 91 | |||
| 92 | SettingsItem.TYPE_STRING_SINGLE_CHOICE -> { | ||
| 93 | val item = settingsViewModel.clickedItem as StringSingleChoiceSetting | ||
| 94 | MaterialAlertDialogBuilder(requireContext()) | ||
| 95 | .setTitle(item.nameId) | ||
| 96 | .setSingleChoiceItems(item.choices, item.selectValueIndex, this) | ||
| 97 | .create() | ||
| 98 | } | ||
| 99 | |||
| 100 | else -> super.onCreateDialog(savedInstanceState) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | override fun onCreateView( | ||
| 105 | inflater: LayoutInflater, | ||
| 106 | container: ViewGroup?, | ||
| 107 | savedInstanceState: Bundle? | ||
| 108 | ): View? { | ||
| 109 | return when (type) { | ||
| 110 | SettingsItem.TYPE_SLIDER -> sliderBinding.root | ||
| 111 | else -> super.onCreateView(inflater, container, savedInstanceState) | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 116 | super.onViewCreated(view, savedInstanceState) | ||
| 117 | when (type) { | ||
| 118 | SettingsItem.TYPE_SLIDER -> { | ||
| 119 | viewLifecycleOwner.lifecycleScope.launch { | ||
| 120 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 121 | settingsViewModel.sliderTextValue.collect { | ||
| 122 | sliderBinding.textValue.text = it | ||
| 123 | } | ||
| 124 | } | ||
| 125 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 126 | settingsViewModel.sliderProgress.collect { | ||
| 127 | sliderBinding.slider.value = it.toFloat() | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | override fun onClick(dialog: DialogInterface, which: Int) { | ||
| 136 | when (settingsViewModel.clickedItem) { | ||
| 137 | is SingleChoiceSetting -> { | ||
| 138 | val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting | ||
| 139 | val value = getValueForSingleChoiceSelection(scSetting, which) | ||
| 140 | if (scSetting.selectedValue != value) { | ||
| 141 | settingsViewModel.shouldSave = true | ||
| 142 | } | ||
| 143 | scSetting.selectedValue = value | ||
| 144 | } | ||
| 145 | |||
| 146 | is StringSingleChoiceSetting -> { | ||
| 147 | val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting | ||
| 148 | val value = scSetting.getValueAt(which) | ||
| 149 | if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true | ||
| 150 | scSetting.selectedValue = value | ||
| 151 | } | ||
| 152 | |||
| 153 | is SliderSetting -> { | ||
| 154 | val sliderSetting = settingsViewModel.clickedItem as SliderSetting | ||
| 155 | if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) { | ||
| 156 | settingsViewModel.shouldSave = true | ||
| 157 | } | ||
| 158 | sliderSetting.selectedValue = settingsViewModel.sliderProgress.value | ||
| 159 | } | ||
| 160 | } | ||
| 161 | closeDialog() | ||
| 162 | } | ||
| 163 | |||
| 164 | private fun closeDialog() { | ||
| 165 | settingsViewModel.setAdapterItemChanged(position) | ||
| 166 | settingsViewModel.clickedItem = null | ||
| 167 | settingsViewModel.setSliderProgress(-1f) | ||
| 168 | dismiss() | ||
| 169 | } | ||
| 170 | |||
| 171 | private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { | ||
| 172 | val valuesId = item.valuesId | ||
| 173 | return if (valuesId > 0) { | ||
| 174 | val valuesArray = requireContext().resources.getIntArray(valuesId) | ||
| 175 | valuesArray[which] | ||
| 176 | } else { | ||
| 177 | which | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { | ||
| 182 | val value = item.selectedValue | ||
| 183 | val valuesId = item.valuesId | ||
| 184 | if (valuesId > 0) { | ||
| 185 | val valuesArray = requireContext().resources.getIntArray(valuesId) | ||
| 186 | for (index in valuesArray.indices) { | ||
| 187 | val current = valuesArray[index] | ||
| 188 | if (current == value) { | ||
| 189 | return index | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } else { | ||
| 193 | return value | ||
| 194 | } | ||
| 195 | return -1 | ||
| 196 | } | ||
| 197 | |||
| 198 | companion object { | ||
| 199 | const val TAG = "SettingsDialogFragment" | ||
| 200 | |||
| 201 | const val TYPE_RESET_SETTING = -1 | ||
| 202 | |||
| 203 | const val TITLE = "Title" | ||
| 204 | const val TYPE = "Type" | ||
| 205 | const val POSITION = "Position" | ||
| 206 | |||
| 207 | fun newInstance( | ||
| 208 | settingsViewModel: SettingsViewModel, | ||
| 209 | clickedItem: SettingsItem, | ||
| 210 | type: Int, | ||
| 211 | position: Int | ||
| 212 | ): SettingsDialogFragment { | ||
| 213 | when (type) { | ||
| 214 | SettingsItem.TYPE_HEADER, | ||
| 215 | SettingsItem.TYPE_SWITCH, | ||
| 216 | SettingsItem.TYPE_SUBMENU, | ||
| 217 | SettingsItem.TYPE_DATETIME_SETTING, | ||
| 218 | SettingsItem.TYPE_RUNNABLE -> | ||
| 219 | throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!") | ||
| 220 | |||
| 221 | SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress( | ||
| 222 | (clickedItem as SliderSetting).selectedValue.toFloat() | ||
| 223 | ) | ||
| 224 | } | ||
| 225 | settingsViewModel.clickedItem = clickedItem | ||
| 226 | |||
| 227 | val args = Bundle() | ||
| 228 | args.putInt(TYPE, type) | ||
| 229 | args.putInt(POSITION, position) | ||
| 230 | val fragment = SettingsDialogFragment() | ||
| 231 | fragment.arguments = args | ||
| 232 | return fragment | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt new file mode 100644 index 000000000..9d0594c6e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt | |||
| @@ -0,0 +1,192 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.content.Context | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import android.view.inputmethod.InputMethodManager | ||
| 12 | import androidx.core.view.ViewCompat | ||
| 13 | import androidx.core.view.WindowInsetsCompat | ||
| 14 | import androidx.core.view.updatePadding | ||
| 15 | import androidx.core.widget.doOnTextChanged | ||
| 16 | import androidx.fragment.app.Fragment | ||
| 17 | import androidx.fragment.app.activityViewModels | ||
| 18 | import androidx.lifecycle.Lifecycle | ||
| 19 | import androidx.lifecycle.lifecycleScope | ||
| 20 | import androidx.lifecycle.repeatOnLifecycle | ||
| 21 | import androidx.recyclerview.widget.LinearLayoutManager | ||
| 22 | import com.google.android.material.divider.MaterialDividerItemDecoration | ||
| 23 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 24 | import info.debatty.java.stringsimilarity.Cosine | ||
| 25 | import kotlinx.coroutines.launch | ||
| 26 | import org.yuzu.yuzu_emu.R | ||
| 27 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding | ||
| 28 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 29 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 30 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 31 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 32 | |||
| 33 | class SettingsSearchFragment : Fragment() { | ||
| 34 | private var _binding: FragmentSettingsSearchBinding? = null | ||
| 35 | private val binding get() = _binding!! | ||
| 36 | |||
| 37 | private var settingsAdapter: SettingsAdapter? = null | ||
| 38 | |||
| 39 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
| 40 | |||
| 41 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 42 | super.onCreate(savedInstanceState) | ||
| 43 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) | ||
| 44 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) | ||
| 45 | } | ||
| 46 | |||
| 47 | override fun onCreateView( | ||
| 48 | inflater: LayoutInflater, | ||
| 49 | container: ViewGroup?, | ||
| 50 | savedInstanceState: Bundle? | ||
| 51 | ): View { | ||
| 52 | _binding = FragmentSettingsSearchBinding.inflate(layoutInflater) | ||
| 53 | return binding.root | ||
| 54 | } | ||
| 55 | |||
| 56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 57 | super.onViewCreated(view, savedInstanceState) | ||
| 58 | settingsViewModel.setIsUsingSearch(true) | ||
| 59 | |||
| 60 | if (savedInstanceState != null) { | ||
| 61 | binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT)) | ||
| 62 | } | ||
| 63 | |||
| 64 | settingsAdapter = SettingsAdapter(this, requireContext()) | ||
| 65 | |||
| 66 | val dividerDecoration = MaterialDividerItemDecoration( | ||
| 67 | requireContext(), | ||
| 68 | LinearLayoutManager.VERTICAL | ||
| 69 | ) | ||
| 70 | dividerDecoration.isLastItemDecorated = false | ||
| 71 | binding.settingsList.apply { | ||
| 72 | adapter = settingsAdapter | ||
| 73 | layoutManager = LinearLayoutManager(requireContext()) | ||
| 74 | addItemDecoration(dividerDecoration) | ||
| 75 | } | ||
| 76 | |||
| 77 | focusSearch() | ||
| 78 | |||
| 79 | binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) } | ||
| 80 | binding.searchBackground.setOnClickListener { focusSearch() } | ||
| 81 | binding.clearButton.setOnClickListener { binding.searchText.setText("") } | ||
| 82 | binding.searchText.doOnTextChanged { _, _, _, _ -> | ||
| 83 | search() | ||
| 84 | binding.settingsList.smoothScrollToPosition(0) | ||
| 85 | } | ||
| 86 | viewLifecycleOwner.lifecycleScope.launch { | ||
| 87 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 88 | settingsViewModel.shouldReloadSettingsList.collect { | ||
| 89 | if (it) { | ||
| 90 | settingsViewModel.setShouldReloadSettingsList(false) | ||
| 91 | search() | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | search() | ||
| 98 | |||
| 99 | setInsets() | ||
| 100 | } | ||
| 101 | |||
| 102 | override fun onSaveInstanceState(outState: Bundle) { | ||
| 103 | super.onSaveInstanceState(outState) | ||
| 104 | outState.putString(SEARCH_TEXT, binding.searchText.text.toString()) | ||
| 105 | } | ||
| 106 | |||
| 107 | private fun search() { | ||
| 108 | val searchTerm = binding.searchText.text.toString().lowercase() | ||
| 109 | binding.clearButton.visibility = | ||
| 110 | if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE | ||
| 111 | if (searchTerm.isEmpty()) { | ||
| 112 | binding.noResultsView.visibility = View.VISIBLE | ||
| 113 | settingsAdapter?.submitList(emptyList()) | ||
| 114 | return | ||
| 115 | } | ||
| 116 | |||
| 117 | val baseList = SettingsItem.settingsItems | ||
| 118 | val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1) | ||
| 119 | val sortedList: List<SettingsItem> = baseList.mapNotNull { item -> | ||
| 120 | val title = getString(item.value.nameId).lowercase() | ||
| 121 | val similarity = similarityAlgorithm.similarity(searchTerm, title) | ||
| 122 | if (similarity > 0.08) { | ||
| 123 | Pair(similarity, item) | ||
| 124 | } else { | ||
| 125 | null | ||
| 126 | } | ||
| 127 | }.sortedByDescending { it.first }.mapNotNull { | ||
| 128 | val item = it.second.value | ||
| 129 | val pairedSettingKey = item.setting.pairedSettingKey | ||
| 130 | val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) { | ||
| 131 | val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) | ||
| 132 | if (pairedSettingValue) it.second.value else null | ||
| 133 | } else { | ||
| 134 | it.second.value | ||
| 135 | } | ||
| 136 | optionalSetting | ||
| 137 | } | ||
| 138 | settingsAdapter?.submitList(sortedList) | ||
| 139 | binding.noResultsView.visibility = | ||
| 140 | if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE | ||
| 141 | } | ||
| 142 | |||
| 143 | private fun focusSearch() { | ||
| 144 | binding.searchText.requestFocus() | ||
| 145 | val imm = requireActivity() | ||
| 146 | .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? | ||
| 147 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) | ||
| 148 | } | ||
| 149 | |||
| 150 | private fun setInsets() = | ||
| 151 | ViewCompat.setOnApplyWindowInsetsListener( | ||
| 152 | binding.root | ||
| 153 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 154 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) | ||
| 155 | val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) | ||
| 156 | val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip) | ||
| 157 | |||
| 158 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| 159 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 160 | |||
| 161 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 162 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 163 | |||
| 164 | binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing) | ||
| 165 | binding.frameSearch.updatePadding( | ||
| 166 | left = leftInsets + sideMargin, | ||
| 167 | top = barInsets.top + topMargin, | ||
| 168 | right = rightInsets + sideMargin | ||
| 169 | ) | ||
| 170 | binding.noResultsView.updatePadding( | ||
| 171 | left = leftInsets, | ||
| 172 | right = rightInsets, | ||
| 173 | bottom = barInsets.bottom | ||
| 174 | ) | ||
| 175 | |||
| 176 | val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams | ||
| 177 | mlpSettingsList.leftMargin = leftInsets + sideMargin | ||
| 178 | mlpSettingsList.rightMargin = rightInsets + sideMargin | ||
| 179 | binding.settingsList.layoutParams = mlpSettingsList | ||
| 180 | |||
| 181 | val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams | ||
| 182 | mlpDivider.leftMargin = leftInsets + sideMargin | ||
| 183 | mlpDivider.rightMargin = rightInsets + sideMargin | ||
| 184 | binding.divider.layoutParams = mlpDivider | ||
| 185 | |||
| 186 | windowInsets | ||
| 187 | } | ||
| 188 | |||
| 189 | companion object { | ||
| 190 | const val SEARCH_TEXT = "SearchText" | ||
| 191 | } | ||
| 192 | } | ||
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 6c4ddaf6b..fbb2f6e18 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 | |||
| @@ -19,12 +19,17 @@ import androidx.core.content.ContextCompat | |||
| 19 | import androidx.core.view.ViewCompat | 19 | import androidx.core.view.ViewCompat |
| 20 | import androidx.core.view.WindowInsetsCompat | 20 | import androidx.core.view.WindowInsetsCompat |
| 21 | import androidx.core.view.isVisible | 21 | import androidx.core.view.isVisible |
| 22 | import androidx.core.view.updatePadding | ||
| 22 | import androidx.fragment.app.Fragment | 23 | import androidx.fragment.app.Fragment |
| 23 | import androidx.fragment.app.activityViewModels | 24 | import androidx.fragment.app.activityViewModels |
| 25 | import androidx.lifecycle.Lifecycle | ||
| 26 | import androidx.lifecycle.lifecycleScope | ||
| 27 | import androidx.lifecycle.repeatOnLifecycle | ||
| 24 | import androidx.navigation.findNavController | 28 | import androidx.navigation.findNavController |
| 25 | import androidx.preference.PreferenceManager | 29 | import androidx.preference.PreferenceManager |
| 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | 30 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback |
| 27 | import com.google.android.material.transition.MaterialFadeThrough | 31 | import com.google.android.material.transition.MaterialFadeThrough |
| 32 | import kotlinx.coroutines.launch | ||
| 28 | import java.io.File | 33 | import java.io.File |
| 29 | import org.yuzu.yuzu_emu.R | 34 | import org.yuzu.yuzu_emu.R |
| 30 | import org.yuzu.yuzu_emu.YuzuApplication | 35 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -32,10 +37,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter | |||
| 32 | import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding | 37 | import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding |
| 33 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 38 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 34 | import org.yuzu.yuzu_emu.model.HomeViewModel | 39 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 40 | import org.yuzu.yuzu_emu.model.SetupCallback | ||
| 35 | import org.yuzu.yuzu_emu.model.SetupPage | 41 | import org.yuzu.yuzu_emu.model.SetupPage |
| 42 | import org.yuzu.yuzu_emu.model.StepState | ||
| 36 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 43 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 37 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 44 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 38 | import org.yuzu.yuzu_emu.utils.GameHelper | 45 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 46 | import org.yuzu.yuzu_emu.utils.ViewUtils | ||
| 39 | 47 | ||
| 40 | class SetupFragment : Fragment() { | 48 | class SetupFragment : Fragment() { |
| 41 | private var _binding: FragmentSetupBinding? = null | 49 | private var _binding: FragmentSetupBinding? = null |
| @@ -112,14 +120,22 @@ class SetupFragment : Fragment() { | |||
| 112 | 0, | 120 | 0, |
| 113 | false, | 121 | false, |
| 114 | R.string.give_permission, | 122 | R.string.give_permission, |
| 115 | { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, | 123 | { |
| 124 | notificationCallback = it | ||
| 125 | permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) | ||
| 126 | }, | ||
| 116 | true, | 127 | true, |
| 117 | R.string.notification_warning, | 128 | R.string.notification_warning, |
| 118 | R.string.notification_warning_description, | 129 | R.string.notification_warning_description, |
| 119 | 0, | 130 | 0, |
| 120 | { | 131 | { |
| 121 | NotificationManagerCompat.from(requireContext()) | 132 | if (NotificationManagerCompat.from(requireContext()) |
| 122 | .areNotificationsEnabled() | 133 | .areNotificationsEnabled() |
| 134 | ) { | ||
| 135 | StepState.COMPLETE | ||
| 136 | } else { | ||
| 137 | StepState.INCOMPLETE | ||
| 138 | } | ||
| 123 | } | 139 | } |
| 124 | ) | 140 | ) |
| 125 | ) | 141 | ) |
| @@ -133,12 +149,22 @@ class SetupFragment : Fragment() { | |||
| 133 | R.drawable.ic_add, | 149 | R.drawable.ic_add, |
| 134 | true, | 150 | true, |
| 135 | R.string.select_keys, | 151 | R.string.select_keys, |
| 136 | { mainActivity.getProdKey.launch(arrayOf("*/*")) }, | 152 | { |
| 153 | keyCallback = it | ||
| 154 | getProdKey.launch(arrayOf("*/*")) | ||
| 155 | }, | ||
| 137 | true, | 156 | true, |
| 138 | R.string.install_prod_keys_warning, | 157 | R.string.install_prod_keys_warning, |
| 139 | R.string.install_prod_keys_warning_description, | 158 | R.string.install_prod_keys_warning_description, |
| 140 | R.string.install_prod_keys_warning_help, | 159 | R.string.install_prod_keys_warning_help, |
| 141 | { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } | 160 | { |
| 161 | val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys") | ||
| 162 | if (file.exists()) { | ||
| 163 | StepState.COMPLETE | ||
| 164 | } else { | ||
| 165 | StepState.INCOMPLETE | ||
| 166 | } | ||
| 167 | } | ||
| 142 | ) | 168 | ) |
| 143 | ) | 169 | ) |
| 144 | add( | 170 | add( |
| @@ -150,9 +176,8 @@ class SetupFragment : Fragment() { | |||
| 150 | true, | 176 | true, |
| 151 | R.string.add_games, | 177 | R.string.add_games, |
| 152 | { | 178 | { |
| 153 | mainActivity.getGamesDirectory.launch( | 179 | gamesDirCallback = it |
| 154 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | 180 | getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) |
| 155 | ) | ||
| 156 | }, | 181 | }, |
| 157 | true, | 182 | true, |
| 158 | R.string.add_games_warning, | 183 | R.string.add_games_warning, |
| @@ -163,7 +188,11 @@ class SetupFragment : Fragment() { | |||
| 163 | PreferenceManager.getDefaultSharedPreferences( | 188 | PreferenceManager.getDefaultSharedPreferences( |
| 164 | YuzuApplication.appContext | 189 | YuzuApplication.appContext |
| 165 | ) | 190 | ) |
| 166 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() | 191 | if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { |
| 192 | StepState.COMPLETE | ||
| 193 | } else { | ||
| 194 | StepState.INCOMPLETE | ||
| 195 | } | ||
| 167 | } | 196 | } |
| 168 | ) | 197 | ) |
| 169 | ) | 198 | ) |
| @@ -181,6 +210,17 @@ class SetupFragment : Fragment() { | |||
| 181 | ) | 210 | ) |
| 182 | } | 211 | } |
| 183 | 212 | ||
| 213 | viewLifecycleOwner.lifecycleScope.launch { | ||
| 214 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 215 | homeViewModel.shouldPageForward.collect { | ||
| 216 | if (it) { | ||
| 217 | pageForward() | ||
| 218 | homeViewModel.setShouldPageForward(false) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 184 | binding.viewPager2.apply { | 224 | binding.viewPager2.apply { |
| 185 | adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) | 225 | adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) |
| 186 | offscreenPageLimit = 2 | 226 | offscreenPageLimit = 2 |
| @@ -194,15 +234,15 @@ class SetupFragment : Fragment() { | |||
| 194 | super.onPageSelected(position) | 234 | super.onPageSelected(position) |
| 195 | 235 | ||
| 196 | if (position == 1 && previousPosition == 0) { | 236 | if (position == 1 && previousPosition == 0) { |
| 197 | showView(binding.buttonNext) | 237 | ViewUtils.showView(binding.buttonNext) |
| 198 | showView(binding.buttonBack) | 238 | ViewUtils.showView(binding.buttonBack) |
| 199 | } else if (position == 0 && previousPosition == 1) { | 239 | } else if (position == 0 && previousPosition == 1) { |
| 200 | hideView(binding.buttonBack) | 240 | ViewUtils.hideView(binding.buttonBack) |
| 201 | hideView(binding.buttonNext) | 241 | ViewUtils.hideView(binding.buttonNext) |
| 202 | } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { | 242 | } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { |
| 203 | hideView(binding.buttonNext) | 243 | ViewUtils.hideView(binding.buttonNext) |
| 204 | } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { | 244 | } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { |
| 205 | showView(binding.buttonNext) | 245 | ViewUtils.showView(binding.buttonNext) |
| 206 | } | 246 | } |
| 207 | 247 | ||
| 208 | previousPosition = position | 248 | previousPosition = position |
| @@ -215,7 +255,8 @@ class SetupFragment : Fragment() { | |||
| 215 | 255 | ||
| 216 | // Checks if the user has completed the task on the current page | 256 | // Checks if the user has completed the task on the current page |
| 217 | if (currentPage.hasWarning) { | 257 | if (currentPage.hasWarning) { |
| 218 | if (currentPage.taskCompleted.invoke()) { | 258 | val stepState = currentPage.stepCompleted.invoke() |
| 259 | if (stepState != StepState.INCOMPLETE) { | ||
| 219 | pageForward() | 260 | pageForward() |
| 220 | return@setOnClickListener | 261 | return@setOnClickListener |
| 221 | } | 262 | } |
| @@ -264,9 +305,15 @@ class SetupFragment : Fragment() { | |||
| 264 | _binding = null | 305 | _binding = null |
| 265 | } | 306 | } |
| 266 | 307 | ||
| 308 | private lateinit var notificationCallback: SetupCallback | ||
| 309 | |||
| 267 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) | 310 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| 268 | private val permissionLauncher = | 311 | private val permissionLauncher = |
| 269 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { | 312 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { |
| 313 | if (it) { | ||
| 314 | notificationCallback.onStepCompleted() | ||
| 315 | } | ||
| 316 | |||
| 270 | if (!it && | 317 | if (!it && |
| 271 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) | 318 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) |
| 272 | ) { | 319 | ) { |
| @@ -277,6 +324,27 @@ class SetupFragment : Fragment() { | |||
| 277 | } | 324 | } |
| 278 | } | 325 | } |
| 279 | 326 | ||
| 327 | private lateinit var keyCallback: SetupCallback | ||
| 328 | |||
| 329 | val getProdKey = | ||
| 330 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | ||
| 331 | if (result != null) { | ||
| 332 | if (mainActivity.processKey(result)) { | ||
| 333 | keyCallback.onStepCompleted() | ||
| 334 | } | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | private lateinit var gamesDirCallback: SetupCallback | ||
| 339 | |||
| 340 | val getGamesDirectory = | ||
| 341 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | ||
| 342 | if (result != null) { | ||
| 343 | mainActivity.processGamesDir(result) | ||
| 344 | gamesDirCallback.onStepCompleted() | ||
| 345 | } | ||
| 346 | } | ||
| 347 | |||
| 280 | private fun finishSetup() { | 348 | private fun finishSetup() { |
| 281 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | 349 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() |
| 282 | .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) | 350 | .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) |
| @@ -284,33 +352,6 @@ class SetupFragment : Fragment() { | |||
| 284 | mainActivity.finishSetup(binding.root.findNavController()) | 352 | mainActivity.finishSetup(binding.root.findNavController()) |
| 285 | } | 353 | } |
| 286 | 354 | ||
| 287 | private fun showView(view: View) { | ||
| 288 | view.apply { | ||
| 289 | alpha = 0f | ||
| 290 | visibility = View.VISIBLE | ||
| 291 | isClickable = true | ||
| 292 | }.animate().apply { | ||
| 293 | duration = 300 | ||
| 294 | alpha(1f) | ||
| 295 | }.start() | ||
| 296 | } | ||
| 297 | |||
| 298 | private fun hideView(view: View) { | ||
| 299 | if (view.visibility == View.INVISIBLE) { | ||
| 300 | return | ||
| 301 | } | ||
| 302 | |||
| 303 | view.apply { | ||
| 304 | alpha = 1f | ||
| 305 | isClickable = false | ||
| 306 | }.animate().apply { | ||
| 307 | duration = 300 | ||
| 308 | alpha(0f) | ||
| 309 | }.withEndAction { | ||
| 310 | view.visibility = View.INVISIBLE | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | fun pageForward() { | 355 | fun pageForward() { |
| 315 | binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 | 356 | binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 |
| 316 | } | 357 | } |
| @@ -326,15 +367,29 @@ class SetupFragment : Fragment() { | |||
| 326 | private fun setInsets() = | 367 | private fun setInsets() = |
| 327 | ViewCompat.setOnApplyWindowInsetsListener( | 368 | ViewCompat.setOnApplyWindowInsetsListener( |
| 328 | binding.root | 369 | binding.root |
| 329 | ) { view: View, windowInsets: WindowInsetsCompat -> | 370 | ) { _: View, windowInsets: WindowInsetsCompat -> |
| 330 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 371 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 331 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 372 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 332 | view.setPadding( | 373 | |
| 333 | barInsets.left + cutoutInsets.left, | 374 | val leftPadding = barInsets.left + cutoutInsets.left |
| 334 | barInsets.top + cutoutInsets.top, | 375 | val topPadding = barInsets.top + cutoutInsets.top |
| 335 | barInsets.right + cutoutInsets.right, | 376 | val rightPadding = barInsets.right + cutoutInsets.right |
| 336 | barInsets.bottom + cutoutInsets.bottom | 377 | val bottomPadding = barInsets.bottom + cutoutInsets.bottom |
| 337 | ) | 378 | |
| 379 | if (resources.getBoolean(R.bool.small_layout)) { | ||
| 380 | binding.viewPager2 | ||
| 381 | .updatePadding(left = leftPadding, top = topPadding, right = rightPadding) | ||
| 382 | binding.constraintButtons | ||
| 383 | .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding) | ||
| 384 | } else { | ||
| 385 | binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding) | ||
| 386 | binding.constraintButtons | ||
| 387 | .updatePadding( | ||
| 388 | left = leftPadding, | ||
| 389 | right = rightPadding, | ||
| 390 | bottom = bottomPadding | ||
| 391 | ) | ||
| 392 | } | ||
| 338 | windowInsets | 393 | windowInsets |
| 339 | } | 394 | } |
| 340 | } | 395 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt new file mode 100644 index 000000000..f34870c2d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.lifecycle.ViewModel | ||
| 7 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 8 | import kotlinx.coroutines.flow.StateFlow | ||
| 9 | |||
| 10 | class EmulationViewModel : ViewModel() { | ||
| 11 | val emulationStarted: StateFlow<Boolean> get() = _emulationStarted | ||
| 12 | private val _emulationStarted = MutableStateFlow(false) | ||
| 13 | |||
| 14 | val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping | ||
| 15 | private val _isEmulationStopping = MutableStateFlow(false) | ||
| 16 | |||
| 17 | val shaderProgress: StateFlow<Int> get() = _shaderProgress | ||
| 18 | private val _shaderProgress = MutableStateFlow(0) | ||
| 19 | |||
| 20 | val totalShaders: StateFlow<Int> get() = _totalShaders | ||
| 21 | private val _totalShaders = MutableStateFlow(0) | ||
| 22 | |||
| 23 | val shaderMessage: StateFlow<String> get() = _shaderMessage | ||
| 24 | private val _shaderMessage = MutableStateFlow("") | ||
| 25 | |||
| 26 | fun setEmulationStarted(started: Boolean) { | ||
| 27 | _emulationStarted.value = started | ||
| 28 | } | ||
| 29 | |||
| 30 | fun setIsEmulationStopping(value: Boolean) { | ||
| 31 | _isEmulationStopping.value = value | ||
| 32 | } | ||
| 33 | |||
| 34 | fun setShaderProgress(progress: Int) { | ||
| 35 | _shaderProgress.value = progress | ||
| 36 | } | ||
| 37 | |||
| 38 | fun setTotalShaders(max: Int) { | ||
| 39 | _totalShaders.value = max | ||
| 40 | } | ||
| 41 | |||
| 42 | fun setShaderMessage(msg: String) { | ||
| 43 | _shaderMessage.value = msg | ||
| 44 | } | ||
| 45 | |||
| 46 | fun updateProgress(msg: String, progress: Int, max: Int) { | ||
| 47 | setShaderMessage(msg) | ||
| 48 | setShaderProgress(progress) | ||
| 49 | setTotalShaders(max) | ||
| 50 | } | ||
| 51 | |||
| 52 | fun clear() { | ||
| 53 | setEmulationStarted(false) | ||
| 54 | setIsEmulationStopping(false) | ||
| 55 | setShaderProgress(0) | ||
| 56 | setTotalShaders(0) | ||
| 57 | setShaderMessage("") | ||
| 58 | } | ||
| 59 | |||
| 60 | companion object { | ||
| 61 | const val KEY_EMULATION_STARTED = "EmulationStarted" | ||
| 62 | const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting" | ||
| 63 | const val KEY_SHADER_PROGRESS = "ShaderProgress" | ||
| 64 | const val KEY_TOTAL_SHADERS = "TotalShaders" | ||
| 65 | const val KEY_SHADER_MESSAGE = "ShaderMessage" | ||
| 66 | } | ||
| 67 | } | ||
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 1fe42f922..6e09fa81d 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 | |||
| @@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model | |||
| 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 androidx.lifecycle.LiveData | ||
| 9 | import androidx.lifecycle.MutableLiveData | ||
| 10 | import androidx.lifecycle.ViewModel | 8 | import androidx.lifecycle.ViewModel |
| 11 | import androidx.lifecycle.viewModelScope | 9 | import androidx.lifecycle.viewModelScope |
| 12 | import androidx.preference.PreferenceManager | 10 | import androidx.preference.PreferenceManager |
| 13 | import java.util.Locale | 11 | import java.util.Locale |
| 14 | import kotlinx.coroutines.Dispatchers | 12 | import kotlinx.coroutines.Dispatchers |
| 13 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 14 | import kotlinx.coroutines.flow.StateFlow | ||
| 15 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| 16 | import kotlinx.coroutines.withContext | 16 | import kotlinx.coroutines.withContext |
| 17 | import kotlinx.serialization.ExperimentalSerializationApi | 17 | import kotlinx.serialization.ExperimentalSerializationApi |
| @@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper | |||
| 24 | 24 | ||
| 25 | @OptIn(ExperimentalSerializationApi::class) | 25 | @OptIn(ExperimentalSerializationApi::class) |
| 26 | class GamesViewModel : ViewModel() { | 26 | class GamesViewModel : ViewModel() { |
| 27 | private val _games = MutableLiveData<List<Game>>(emptyList()) | 27 | val games: StateFlow<List<Game>> get() = _games |
| 28 | val games: LiveData<List<Game>> get() = _games | 28 | private val _games = MutableStateFlow(emptyList<Game>()) |
| 29 | 29 | ||
| 30 | private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) | 30 | val searchedGames: StateFlow<List<Game>> get() = _searchedGames |
| 31 | val searchedGames: LiveData<List<Game>> get() = _searchedGames | 31 | private val _searchedGames = MutableStateFlow(emptyList<Game>()) |
| 32 | 32 | ||
| 33 | private val _isReloading = MutableLiveData(false) | 33 | val isReloading: StateFlow<Boolean> get() = _isReloading |
| 34 | val isReloading: LiveData<Boolean> get() = _isReloading | 34 | private val _isReloading = MutableStateFlow(false) |
| 35 | 35 | ||
| 36 | private val _shouldSwapData = MutableLiveData(false) | 36 | val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData |
| 37 | val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData | 37 | private val _shouldSwapData = MutableStateFlow(false) |
| 38 | 38 | ||
| 39 | private val _shouldScrollToTop = MutableLiveData(false) | 39 | val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop |
| 40 | val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop | 40 | private val _shouldScrollToTop = MutableStateFlow(false) |
| 41 | 41 | ||
| 42 | private val _searchFocused = MutableLiveData(false) | 42 | val searchFocused: StateFlow<Boolean> get() = _searchFocused |
| 43 | val searchFocused: LiveData<Boolean> get() = _searchFocused | 43 | private val _searchFocused = MutableStateFlow(false) |
| 44 | 44 | ||
| 45 | init { | 45 | init { |
| 46 | // Ensure keys are loaded so that ROM metadata can be decrypted. | 46 | // Ensure keys are loaded so that ROM metadata can be decrypted. |
| @@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() { | |||
| 79 | ) | 79 | ) |
| 80 | ) | 80 | ) |
| 81 | 81 | ||
| 82 | _games.postValue(sortedList) | 82 | _games.value = sortedList |
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | fun setSearchedGames(games: List<Game>) { | 85 | fun setSearchedGames(games: List<Game>) { |
| 86 | _searchedGames.postValue(games) | 86 | _searchedGames.value = games |
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | fun setShouldSwapData(shouldSwap: Boolean) { | 89 | fun setShouldSwapData(shouldSwap: Boolean) { |
| 90 | _shouldSwapData.postValue(shouldSwap) | 90 | _shouldSwapData.value = shouldSwap |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | fun setShouldScrollToTop(shouldScroll: Boolean) { | 93 | fun setShouldScrollToTop(shouldScroll: Boolean) { |
| 94 | _shouldScrollToTop.postValue(shouldScroll) | 94 | _shouldScrollToTop.value = shouldScroll |
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | fun setSearchFocused(searchFocused: Boolean) { | 97 | fun setSearchFocused(searchFocused: Boolean) { |
| 98 | _searchFocused.postValue(searchFocused) | 98 | _searchFocused.value = searchFocused |
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | fun reloadGames(directoryChanged: Boolean) { | 101 | fun reloadGames(directoryChanged: Boolean) { |
| 102 | if (isReloading.value == true) { | 102 | if (isReloading.value) { |
| 103 | return | 103 | return |
| 104 | } | 104 | } |
| 105 | _isReloading.postValue(true) | 105 | _isReloading.value = true |
| 106 | 106 | ||
| 107 | viewModelScope.launch { | 107 | viewModelScope.launch { |
| 108 | withContext(Dispatchers.IO) { | 108 | withContext(Dispatchers.IO) { |
| 109 | NativeLibrary.resetRomMetadata() | 109 | NativeLibrary.resetRomMetadata() |
| 110 | setGames(GameHelper.getGames()) | 110 | setGames(GameHelper.getGames()) |
| 111 | _isReloading.postValue(false) | 111 | _isReloading.value = false |
| 112 | 112 | ||
| 113 | if (directoryChanged) { | 113 | if (directoryChanged) { |
| 114 | setShouldSwapData(true) | 114 | setShouldSwapData(true) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt index 522d07c37..b32e19373 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt | |||
| @@ -3,6 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 7 | import kotlinx.coroutines.flow.StateFlow | ||
| 8 | |||
| 6 | data class HomeSetting( | 9 | data class HomeSetting( |
| 7 | val titleId: Int, | 10 | val titleId: Int, |
| 8 | val descriptionId: Int, | 11 | val descriptionId: Int, |
| @@ -10,5 +13,6 @@ data class HomeSetting( | |||
| 10 | val onClick: () -> Unit, | 13 | val onClick: () -> Unit, |
| 11 | val isEnabled: () -> Boolean = { true }, | 14 | val isEnabled: () -> Boolean = { true }, |
| 12 | val disabledTitleId: Int = 0, | 15 | val disabledTitleId: Int = 0, |
| 13 | val disabledMessageId: Int = 0 | 16 | val disabledMessageId: Int = 0, |
| 17 | val details: StateFlow<String> = MutableStateFlow("") | ||
| 14 | ) | 18 | ) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 263ee7144..756f76721 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt | |||
| @@ -3,34 +3,56 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import androidx.lifecycle.LiveData | 6 | import android.net.Uri |
| 7 | import androidx.lifecycle.MutableLiveData | 7 | import androidx.fragment.app.FragmentActivity |
| 8 | import androidx.lifecycle.ViewModel | 8 | import androidx.lifecycle.ViewModel |
| 9 | import androidx.lifecycle.ViewModelProvider | ||
| 10 | import androidx.preference.PreferenceManager | ||
| 11 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 12 | import kotlinx.coroutines.flow.StateFlow | ||
| 13 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 14 | import org.yuzu.yuzu_emu.utils.GameHelper | ||
| 9 | 15 | ||
| 10 | class HomeViewModel : ViewModel() { | 16 | class HomeViewModel : ViewModel() { |
| 11 | private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() | 17 | val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible |
| 12 | val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible | 18 | private val _navigationVisible = MutableStateFlow(Pair(false, false)) |
| 13 | 19 | ||
| 14 | private val _statusBarShadeVisible = MutableLiveData(true) | 20 | val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible |
| 15 | val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible | 21 | private val _statusBarShadeVisible = MutableStateFlow(true) |
| 16 | 22 | ||
| 17 | var navigatedToSetup = false | 23 | val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward |
| 24 | private val _shouldPageForward = MutableStateFlow(false) | ||
| 18 | 25 | ||
| 19 | init { | 26 | val gamesDir: StateFlow<String> get() = _gamesDir |
| 20 | _navigationVisible.value = Pair(false, false) | 27 | private val _gamesDir = MutableStateFlow( |
| 21 | } | 28 | Uri.parse( |
| 29 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 30 | .getString(GameHelper.KEY_GAME_PATH, "") | ||
| 31 | ).path ?: "" | ||
| 32 | ) | ||
| 33 | |||
| 34 | var navigatedToSetup = false | ||
| 22 | 35 | ||
| 23 | fun setNavigationVisibility(visible: Boolean, animated: Boolean) { | 36 | fun setNavigationVisibility(visible: Boolean, animated: Boolean) { |
| 24 | if (_navigationVisible.value?.first == visible) { | 37 | if (navigationVisible.value.first == visible) { |
| 25 | return | 38 | return |
| 26 | } | 39 | } |
| 27 | _navigationVisible.value = Pair(visible, animated) | 40 | _navigationVisible.value = Pair(visible, animated) |
| 28 | } | 41 | } |
| 29 | 42 | ||
| 30 | fun setStatusBarShadeVisibility(visible: Boolean) { | 43 | fun setStatusBarShadeVisibility(visible: Boolean) { |
| 31 | if (_statusBarShadeVisible.value == visible) { | 44 | if (statusBarShadeVisible.value == visible) { |
| 32 | return | 45 | return |
| 33 | } | 46 | } |
| 34 | _statusBarShadeVisible.value = visible | 47 | _statusBarShadeVisible.value = visible |
| 35 | } | 48 | } |
| 49 | |||
| 50 | fun setShouldPageForward(pageForward: Boolean) { | ||
| 51 | _shouldPageForward.value = pageForward | ||
| 52 | } | ||
| 53 | |||
| 54 | fun setGamesDir(activity: FragmentActivity, dir: String) { | ||
| 55 | ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) | ||
| 56 | _gamesDir.value = dir | ||
| 57 | } | ||
| 36 | } | 58 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt new file mode 100644 index 000000000..53fa7a8de --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.lifecycle.ViewModel | ||
| 7 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 8 | import kotlinx.coroutines.flow.StateFlow | ||
| 9 | import org.yuzu.yuzu_emu.R | ||
| 10 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 12 | |||
| 13 | class SettingsViewModel : ViewModel() { | ||
| 14 | var game: Game? = null | ||
| 15 | |||
| 16 | var shouldSave = false | ||
| 17 | |||
| 18 | var clickedItem: SettingsItem? = null | ||
| 19 | |||
| 20 | val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate | ||
| 21 | private val _shouldRecreate = MutableStateFlow(false) | ||
| 22 | |||
| 23 | val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack | ||
| 24 | private val _shouldNavigateBack = MutableStateFlow(false) | ||
| 25 | |||
| 26 | val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog | ||
| 27 | private val _shouldShowResetSettingsDialog = MutableStateFlow(false) | ||
| 28 | |||
| 29 | val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList | ||
| 30 | private val _shouldReloadSettingsList = MutableStateFlow(false) | ||
| 31 | |||
| 32 | val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch | ||
| 33 | private val _isUsingSearch = MutableStateFlow(false) | ||
| 34 | |||
| 35 | val sliderProgress: StateFlow<Int> get() = _sliderProgress | ||
| 36 | private val _sliderProgress = MutableStateFlow(-1) | ||
| 37 | |||
| 38 | val sliderTextValue: StateFlow<String> get() = _sliderTextValue | ||
| 39 | private val _sliderTextValue = MutableStateFlow("") | ||
| 40 | |||
| 41 | val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged | ||
| 42 | private val _adapterItemChanged = MutableStateFlow(-1) | ||
| 43 | |||
| 44 | fun setShouldRecreate(value: Boolean) { | ||
| 45 | _shouldRecreate.value = value | ||
| 46 | } | ||
| 47 | |||
| 48 | fun setShouldNavigateBack(value: Boolean) { | ||
| 49 | _shouldNavigateBack.value = value | ||
| 50 | } | ||
| 51 | |||
| 52 | fun setShouldShowResetSettingsDialog(value: Boolean) { | ||
| 53 | _shouldShowResetSettingsDialog.value = value | ||
| 54 | } | ||
| 55 | |||
| 56 | fun setShouldReloadSettingsList(value: Boolean) { | ||
| 57 | _shouldReloadSettingsList.value = value | ||
| 58 | } | ||
| 59 | |||
| 60 | fun setIsUsingSearch(value: Boolean) { | ||
| 61 | _isUsingSearch.value = value | ||
| 62 | } | ||
| 63 | |||
| 64 | fun setSliderTextValue(value: Float, units: String) { | ||
| 65 | _sliderProgress.value = value.toInt() | ||
| 66 | _sliderTextValue.value = String.format( | ||
| 67 | YuzuApplication.appContext.getString(R.string.value_with_units), | ||
| 68 | value.toInt().toString(), | ||
| 69 | units | ||
| 70 | ) | ||
| 71 | } | ||
| 72 | |||
| 73 | fun setSliderProgress(value: Float) { | ||
| 74 | _sliderProgress.value = value.toInt() | ||
| 75 | } | ||
| 76 | |||
| 77 | fun setAdapterItemChanged(value: Int) { | ||
| 78 | _adapterItemChanged.value = value | ||
| 79 | } | ||
| 80 | |||
| 81 | fun clear() { | ||
| 82 | game = null | ||
| 83 | shouldSave = false | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt index a0c878e1c..09a128ae6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt | |||
| @@ -10,10 +10,20 @@ data class SetupPage( | |||
| 10 | val buttonIconId: Int, | 10 | val buttonIconId: Int, |
| 11 | val leftAlignedIcon: Boolean, | 11 | val leftAlignedIcon: Boolean, |
| 12 | val buttonTextId: Int, | 12 | val buttonTextId: Int, |
| 13 | val buttonAction: () -> Unit, | 13 | val buttonAction: (callback: SetupCallback) -> Unit, |
| 14 | val hasWarning: Boolean, | 14 | val hasWarning: Boolean, |
| 15 | val warningTitleId: Int = 0, | 15 | val warningTitleId: Int = 0, |
| 16 | val warningDescriptionId: Int = 0, | 16 | val warningDescriptionId: Int = 0, |
| 17 | val warningHelpLinkId: Int = 0, | 17 | val warningHelpLinkId: Int = 0, |
| 18 | val taskCompleted: () -> Boolean = { true } | 18 | val stepCompleted: () -> StepState = { StepState.UNDEFINED } |
| 19 | ) | 19 | ) |
| 20 | |||
| 21 | interface SetupCallback { | ||
| 22 | fun onStepCompleted() | ||
| 23 | } | ||
| 24 | |||
| 25 | enum class StepState { | ||
| 26 | COMPLETE, | ||
| 27 | INCOMPLETE, | ||
| 28 | UNDEFINED | ||
| 29 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt index 27ea725a5..531c2aaf0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt | |||
| @@ -3,29 +3,25 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import androidx.lifecycle.LiveData | ||
| 7 | import androidx.lifecycle.MutableLiveData | ||
| 8 | import androidx.lifecycle.ViewModel | 6 | import androidx.lifecycle.ViewModel |
| 9 | import androidx.lifecycle.viewModelScope | 7 | import androidx.lifecycle.viewModelScope |
| 10 | import kotlinx.coroutines.Dispatchers | 8 | import kotlinx.coroutines.Dispatchers |
| 9 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 10 | import kotlinx.coroutines.flow.StateFlow | ||
| 11 | import kotlinx.coroutines.launch | 11 | import kotlinx.coroutines.launch |
| 12 | 12 | ||
| 13 | class TaskViewModel : ViewModel() { | 13 | class TaskViewModel : ViewModel() { |
| 14 | private val _result = MutableLiveData<Any>() | 14 | val result: StateFlow<Any> get() = _result |
| 15 | val result: LiveData<Any> = _result | 15 | private val _result = MutableStateFlow(Any()) |
| 16 | 16 | ||
| 17 | private val _isComplete = MutableLiveData<Boolean>() | 17 | val isComplete: StateFlow<Boolean> get() = _isComplete |
| 18 | val isComplete: LiveData<Boolean> = _isComplete | 18 | private val _isComplete = MutableStateFlow(false) |
| 19 | 19 | ||
| 20 | private val _isRunning = MutableLiveData<Boolean>() | 20 | val isRunning: StateFlow<Boolean> get() = _isRunning |
| 21 | val isRunning: LiveData<Boolean> = _isRunning | 21 | private val _isRunning = MutableStateFlow(false) |
| 22 | 22 | ||
| 23 | lateinit var task: () -> Any | 23 | lateinit var task: () -> Any |
| 24 | 24 | ||
| 25 | init { | ||
| 26 | clear() | ||
| 27 | } | ||
| 28 | |||
| 29 | fun clear() { | 25 | fun clear() { |
| 30 | _result.value = Any() | 26 | _result.value = Any() |
| 31 | _isComplete.value = false | 27 | _isComplete.value = false |
| @@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() { | |||
| 33 | } | 29 | } |
| 34 | 30 | ||
| 35 | fun runTask() { | 31 | fun runTask() { |
| 36 | if (_isRunning.value == true) { | 32 | if (isRunning.value) { |
| 37 | return | 33 | return |
| 38 | } | 34 | } |
| 39 | _isRunning.value = true | 35 | _isRunning.value = true |
| 40 | 36 | ||
| 41 | viewModelScope.launch(Dispatchers.IO) { | 37 | viewModelScope.launch(Dispatchers.IO) { |
| 42 | val res = task() | 38 | val res = task() |
| 43 | _result.postValue(res) | 39 | _result.value = res |
| 44 | _isComplete.postValue(true) | 40 | _isComplete.value = true |
| 41 | _isRunning.value = false | ||
| 45 | } | 42 | } |
| 46 | } | 43 | } |
| 47 | } | 44 | } |
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 b0156dca5..805b89b31 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 | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.ui | 4 | package org.yuzu.yuzu_emu.ui |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | ||
| 6 | import android.os.Bundle | 7 | import android.os.Bundle |
| 7 | import android.view.LayoutInflater | 8 | import android.view.LayoutInflater |
| 8 | import android.view.View | 9 | import android.view.View |
| @@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat | |||
| 14 | import androidx.core.view.updatePadding | 15 | import androidx.core.view.updatePadding |
| 15 | import androidx.fragment.app.Fragment | 16 | import androidx.fragment.app.Fragment |
| 16 | import androidx.fragment.app.activityViewModels | 17 | import androidx.fragment.app.activityViewModels |
| 18 | import androidx.lifecycle.Lifecycle | ||
| 19 | import androidx.lifecycle.lifecycleScope | ||
| 20 | import androidx.lifecycle.repeatOnLifecycle | ||
| 17 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 18 | import com.google.android.material.transition.MaterialFadeThrough | 22 | import com.google.android.material.transition.MaterialFadeThrough |
| 23 | import kotlinx.coroutines.launch | ||
| 19 | import org.yuzu.yuzu_emu.R | 24 | import org.yuzu.yuzu_emu.R |
| 20 | import org.yuzu.yuzu_emu.adapters.GameAdapter | 25 | import org.yuzu.yuzu_emu.adapters.GameAdapter |
| 21 | import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding | 26 | import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding |
| @@ -44,6 +49,8 @@ class GamesFragment : Fragment() { | |||
| 44 | return binding.root | 49 | return binding.root |
| 45 | } | 50 | } |
| 46 | 51 | ||
| 52 | // This is using the correct scope, lint is just acting up | ||
| 53 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
| 47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 48 | homeViewModel.setNavigationVisibility(visible = true, animated = false) | 55 | homeViewModel.setNavigationVisibility(visible = true, animated = false) |
| 49 | 56 | ||
| @@ -80,37 +87,48 @@ class GamesFragment : Fragment() { | |||
| 80 | if (_binding == null) { | 87 | if (_binding == null) { |
| 81 | return@post | 88 | return@post |
| 82 | } | 89 | } |
| 83 | binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! | 90 | binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value |
| 84 | } | 91 | } |
| 85 | } | 92 | } |
| 86 | 93 | ||
| 87 | gamesViewModel.apply { | 94 | viewLifecycleOwner.lifecycleScope.apply { |
| 88 | // Watch for when we get updates to any of our games lists | 95 | launch { |
| 89 | isReloading.observe(viewLifecycleOwner) { isReloading -> | 96 | repeatOnLifecycle(Lifecycle.State.RESUMED) { |
| 90 | binding.swipeRefresh.isRefreshing = isReloading | 97 | gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it } |
| 98 | } | ||
| 91 | } | 99 | } |
| 92 | games.observe(viewLifecycleOwner) { | 100 | launch { |
| 93 | (binding.gridGames.adapter as GameAdapter).submitList(it) | 101 | repeatOnLifecycle(Lifecycle.State.RESUMED) { |
| 94 | if (it.isEmpty()) { | 102 | gamesViewModel.games.collect { |
| 95 | binding.noticeText.visibility = View.VISIBLE | 103 | (binding.gridGames.adapter as GameAdapter).submitList(it) |
| 96 | } else { | 104 | if (it.isEmpty()) { |
| 97 | binding.noticeText.visibility = View.GONE | 105 | binding.noticeText.visibility = View.VISIBLE |
| 106 | } else { | ||
| 107 | binding.noticeText.visibility = View.GONE | ||
| 108 | } | ||
| 109 | } | ||
| 98 | } | 110 | } |
| 99 | } | 111 | } |
| 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> | 112 | launch { |
| 101 | if (shouldSwapData) { | 113 | repeatOnLifecycle(Lifecycle.State.RESUMED) { |
| 102 | (binding.gridGames.adapter as GameAdapter).submitList( | 114 | gamesViewModel.shouldSwapData.collect { |
| 103 | gamesViewModel.games.value!! | 115 | if (it) { |
| 104 | ) | 116 | (binding.gridGames.adapter as GameAdapter).submitList( |
| 105 | gamesViewModel.setShouldSwapData(false) | 117 | gamesViewModel.games.value |
| 118 | ) | ||
| 119 | gamesViewModel.setShouldSwapData(false) | ||
| 120 | } | ||
| 121 | } | ||
| 106 | } | 122 | } |
| 107 | } | 123 | } |
| 108 | 124 | launch { | |
| 109 | // Check if the user reselected the games menu item and then scroll to top of the list | 125 | repeatOnLifecycle(Lifecycle.State.RESUMED) { |
| 110 | shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> | 126 | gamesViewModel.shouldScrollToTop.collect { |
| 111 | if (shouldScroll) { | 127 | if (it) { |
| 112 | scrollToTop() | 128 | scrollToTop() |
| 113 | gamesViewModel.setShouldScrollToTop(false) | 129 | gamesViewModel.setShouldScrollToTop(false) |
| 130 | } | ||
| 131 | } | ||
| 114 | } | 132 | } |
| 115 | } | 133 | } |
| 116 | } | 134 | } |
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 f7d7aed1e..b6b6c6c17 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 | |||
| @@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen | |||
| 19 | import androidx.core.view.ViewCompat | 19 | import androidx.core.view.ViewCompat |
| 20 | import androidx.core.view.WindowCompat | 20 | import androidx.core.view.WindowCompat |
| 21 | import androidx.core.view.WindowInsetsCompat | 21 | import androidx.core.view.WindowInsetsCompat |
| 22 | import androidx.lifecycle.Lifecycle | ||
| 22 | import androidx.lifecycle.lifecycleScope | 23 | import androidx.lifecycle.lifecycleScope |
| 24 | import androidx.lifecycle.repeatOnLifecycle | ||
| 23 | import androidx.navigation.NavController | 25 | import androidx.navigation.NavController |
| 24 | import androidx.navigation.fragment.NavHostFragment | 26 | import androidx.navigation.fragment.NavHostFragment |
| 25 | import androidx.navigation.ui.setupWithNavController | 27 | import androidx.navigation.ui.setupWithNavController |
| @@ -33,17 +35,14 @@ import java.io.IOException | |||
| 33 | import kotlinx.coroutines.Dispatchers | 35 | import kotlinx.coroutines.Dispatchers |
| 34 | import kotlinx.coroutines.launch | 36 | import kotlinx.coroutines.launch |
| 35 | import kotlinx.coroutines.withContext | 37 | import kotlinx.coroutines.withContext |
| 38 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 36 | import org.yuzu.yuzu_emu.NativeLibrary | 39 | import org.yuzu.yuzu_emu.NativeLibrary |
| 37 | import org.yuzu.yuzu_emu.R | 40 | import org.yuzu.yuzu_emu.R |
| 38 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 41 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 39 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 42 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 40 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 43 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 41 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 44 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 42 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | ||
| 43 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | ||
| 44 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | ||
| 45 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 45 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 46 | import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment | ||
| 47 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 46 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 48 | import org.yuzu.yuzu_emu.model.GamesViewModel | 47 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 49 | import org.yuzu.yuzu_emu.model.HomeViewModel | 48 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| @@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 54 | 53 | ||
| 55 | private val homeViewModel: HomeViewModel by viewModels() | 54 | private val homeViewModel: HomeViewModel by viewModels() |
| 56 | private val gamesViewModel: GamesViewModel by viewModels() | 55 | private val gamesViewModel: GamesViewModel by viewModels() |
| 57 | private val settingsViewModel: SettingsViewModel by viewModels() | ||
| 58 | 56 | ||
| 59 | override var themeId: Int = 0 | 57 | override var themeId: Int = 0 |
| 60 | 58 | ||
| @@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 62 | val splashScreen = installSplashScreen() | 60 | val splashScreen = installSplashScreen() |
| 63 | splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } | 61 | splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } |
| 64 | 62 | ||
| 65 | settingsViewModel.settings.loadSettings() | ||
| 66 | |||
| 67 | ThemeHelper.setTheme(this) | 63 | ThemeHelper.setTheme(this) |
| 68 | 64 | ||
| 69 | super.onCreate(savedInstanceState) | 65 | super.onCreate(savedInstanceState) |
| @@ -109,25 +105,33 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 109 | when (it.itemId) { | 105 | when (it.itemId) { |
| 110 | R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) | 106 | R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) |
| 111 | R.id.searchFragment -> gamesViewModel.setSearchFocused(true) | 107 | R.id.searchFragment -> gamesViewModel.setSearchFocused(true) |
| 112 | R.id.homeSettingsFragment -> SettingsActivity.launch( | 108 | R.id.homeSettingsFragment -> { |
| 113 | this, | 109 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
| 114 | SettingsFile.FILE_NAME_CONFIG, | 110 | null, |
| 115 | "" | 111 | Settings.MenuTag.SECTION_ROOT |
| 116 | ) | 112 | ) |
| 113 | navHostFragment.navController.navigate(action) | ||
| 114 | } | ||
| 117 | } | 115 | } |
| 118 | } | 116 | } |
| 119 | 117 | ||
| 120 | // Prevents navigation from being drawn for a short time on recreation if set to hidden | 118 | // Prevents navigation from being drawn for a short time on recreation if set to hidden |
| 121 | if (!homeViewModel.navigationVisible.value?.first!!) { | 119 | if (!homeViewModel.navigationVisible.value.first) { |
| 122 | binding.navigationView.visibility = View.INVISIBLE | 120 | binding.navigationView.visibility = View.INVISIBLE |
| 123 | binding.statusBarShade.visibility = View.INVISIBLE | 121 | binding.statusBarShade.visibility = View.INVISIBLE |
| 124 | } | 122 | } |
| 125 | 123 | ||
| 126 | homeViewModel.navigationVisible.observe(this) { | 124 | lifecycleScope.apply { |
| 127 | showNavigation(it.first, it.second) | 125 | launch { |
| 128 | } | 126 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 129 | homeViewModel.statusBarShadeVisible.observe(this) { visible -> | 127 | homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } |
| 130 | showStatusBarShade(visible) | 128 | } |
| 129 | } | ||
| 130 | launch { | ||
| 131 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 132 | homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) } | ||
| 133 | } | ||
| 134 | } | ||
| 131 | } | 135 | } |
| 132 | 136 | ||
| 133 | // Dismiss previous notifications (should not happen unless a crash occurred) | 137 | // Dismiss previous notifications (should not happen unless a crash occurred) |
| @@ -266,73 +270,81 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 266 | 270 | ||
| 267 | val getGamesDirectory = | 271 | val getGamesDirectory = |
| 268 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | 272 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> |
| 269 | if (result == null) { | 273 | if (result != null) { |
| 270 | return@registerForActivityResult | 274 | processGamesDir(result) |
| 271 | } | 275 | } |
| 276 | } | ||
| 272 | 277 | ||
| 273 | contentResolver.takePersistableUriPermission( | 278 | fun processGamesDir(result: Uri) { |
| 274 | result, | 279 | contentResolver.takePersistableUriPermission( |
| 275 | Intent.FLAG_GRANT_READ_URI_PERMISSION | 280 | result, |
| 276 | ) | 281 | Intent.FLAG_GRANT_READ_URI_PERMISSION |
| 282 | ) | ||
| 277 | 283 | ||
| 278 | // When a new directory is picked, we currently will reset the existing games | 284 | // When a new directory is picked, we currently will reset the existing games |
| 279 | // database. This effectively means that only one game directory is supported. | 285 | // database. This effectively means that only one game directory is supported. |
| 280 | PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() | 286 | PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() |
| 281 | .putString(GameHelper.KEY_GAME_PATH, result.toString()) | 287 | .putString(GameHelper.KEY_GAME_PATH, result.toString()) |
| 282 | .apply() | 288 | .apply() |
| 283 | 289 | ||
| 284 | Toast.makeText( | 290 | Toast.makeText( |
| 285 | applicationContext, | 291 | applicationContext, |
| 286 | R.string.games_dir_selected, | 292 | R.string.games_dir_selected, |
| 287 | Toast.LENGTH_LONG | 293 | Toast.LENGTH_LONG |
| 288 | ).show() | 294 | ).show() |
| 289 | 295 | ||
| 290 | gamesViewModel.reloadGames(true) | 296 | gamesViewModel.reloadGames(true) |
| 291 | } | 297 | homeViewModel.setGamesDir(this, result.path!!) |
| 298 | } | ||
| 292 | 299 | ||
| 293 | val getProdKey = | 300 | val getProdKey = |
| 294 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 301 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 295 | if (result == null) { | 302 | if (result != null) { |
| 296 | return@registerForActivityResult | 303 | processKey(result) |
| 297 | } | 304 | } |
| 305 | } | ||
| 298 | 306 | ||
| 299 | if (FileUtil.getExtension(result) != "keys") { | 307 | fun processKey(result: Uri): Boolean { |
| 300 | MessageDialogFragment.newInstance( | 308 | if (FileUtil.getExtension(result) != "keys") { |
| 301 | R.string.reading_keys_failure, | 309 | MessageDialogFragment.newInstance( |
| 302 | R.string.install_prod_keys_failure_extension_description | 310 | titleId = R.string.reading_keys_failure, |
| 303 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 311 | descriptionId = R.string.install_prod_keys_failure_extension_description |
| 304 | return@registerForActivityResult | 312 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 305 | } | 313 | return false |
| 314 | } | ||
| 306 | 315 | ||
| 307 | contentResolver.takePersistableUriPermission( | 316 | contentResolver.takePersistableUriPermission( |
| 317 | result, | ||
| 318 | Intent.FLAG_GRANT_READ_URI_PERMISSION | ||
| 319 | ) | ||
| 320 | |||
| 321 | val dstPath = DirectoryInitialization.userDirectory + "/keys/" | ||
| 322 | if (FileUtil.copyUriToInternalStorage( | ||
| 323 | applicationContext, | ||
| 308 | result, | 324 | result, |
| 309 | Intent.FLAG_GRANT_READ_URI_PERMISSION | 325 | dstPath, |
| 326 | "prod.keys" | ||
| 310 | ) | 327 | ) |
| 311 | 328 | ) { | |
| 312 | val dstPath = DirectoryInitialization.userDirectory + "/keys/" | 329 | if (NativeLibrary.reloadKeys()) { |
| 313 | if (FileUtil.copyUriToInternalStorage( | 330 | Toast.makeText( |
| 314 | applicationContext, | 331 | applicationContext, |
| 315 | result, | 332 | R.string.install_keys_success, |
| 316 | dstPath, | 333 | Toast.LENGTH_SHORT |
| 317 | "prod.keys" | 334 | ).show() |
| 318 | ) | 335 | gamesViewModel.reloadGames(true) |
| 319 | ) { | 336 | return true |
| 320 | if (NativeLibrary.reloadKeys()) { | 337 | } else { |
| 321 | Toast.makeText( | 338 | MessageDialogFragment.newInstance( |
| 322 | applicationContext, | 339 | titleId = R.string.invalid_keys_error, |
| 323 | R.string.install_keys_success, | 340 | descriptionId = R.string.install_keys_failure_description, |
| 324 | Toast.LENGTH_SHORT | 341 | helpLinkId = R.string.dumping_keys_quickstart_link |
| 325 | ).show() | 342 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 326 | gamesViewModel.reloadGames(true) | 343 | return false |
| 327 | } else { | ||
| 328 | MessageDialogFragment.newInstance( | ||
| 329 | R.string.invalid_keys_error, | ||
| 330 | R.string.install_keys_failure_description, | ||
| 331 | R.string.dumping_keys_quickstart_link | ||
| 332 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | ||
| 333 | } | ||
| 334 | } | 344 | } |
| 335 | } | 345 | } |
| 346 | return false | ||
| 347 | } | ||
| 336 | 348 | ||
| 337 | val getFirmware = | 349 | val getFirmware = |
| 338 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 350 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| @@ -364,8 +376,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 364 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 | 376 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 |
| 365 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { | 377 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { |
| 366 | MessageDialogFragment.newInstance( | 378 | MessageDialogFragment.newInstance( |
| 367 | R.string.firmware_installed_failure, | 379 | titleId = R.string.firmware_installed_failure, |
| 368 | R.string.firmware_installed_failure_description | 380 | descriptionId = R.string.firmware_installed_failure_description |
| 369 | ) | 381 | ) |
| 370 | } else { | 382 | } else { |
| 371 | firmwarePath.deleteRecursively() | 383 | firmwarePath.deleteRecursively() |
| @@ -395,8 +407,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 395 | 407 | ||
| 396 | if (FileUtil.getExtension(result) != "bin") { | 408 | if (FileUtil.getExtension(result) != "bin") { |
| 397 | MessageDialogFragment.newInstance( | 409 | MessageDialogFragment.newInstance( |
| 398 | R.string.reading_keys_failure, | 410 | titleId = R.string.reading_keys_failure, |
| 399 | R.string.install_amiibo_keys_failure_extension_description | 411 | descriptionId = R.string.install_amiibo_keys_failure_extension_description |
| 400 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 412 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 401 | return@registerForActivityResult | 413 | return@registerForActivityResult |
| 402 | } | 414 | } |
| @@ -422,9 +434,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 422 | ).show() | 434 | ).show() |
| 423 | } else { | 435 | } else { |
| 424 | MessageDialogFragment.newInstance( | 436 | MessageDialogFragment.newInstance( |
| 425 | R.string.invalid_keys_error, | 437 | titleId = R.string.invalid_keys_error, |
| 426 | R.string.install_keys_failure_description, | 438 | descriptionId = R.string.install_keys_failure_description, |
| 427 | R.string.dumping_keys_quickstart_link | 439 | helpLinkId = R.string.dumping_keys_quickstart_link |
| 428 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 440 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 429 | } | 441 | } |
| 430 | } | 442 | } |
| @@ -496,96 +508,91 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 496 | var errorBaseGame = 0 | 508 | var errorBaseGame = 0 |
| 497 | var errorExtension = 0 | 509 | var errorExtension = 0 |
| 498 | var errorOther = 0 | 510 | var errorOther = 0 |
| 499 | var errorTotal = 0 | 511 | documents.forEach { |
| 500 | lifecycleScope.launch { | 512 | when (NativeLibrary.installFileToNand(it.toString())) { |
| 501 | documents.forEach { | 513 | NativeLibrary.InstallFileToNandResult.Success -> { |
| 502 | when (NativeLibrary.installFileToNand(it.toString())) { | 514 | installSuccess += 1 |
| 503 | NativeLibrary.InstallFileToNandResult.Success -> { | ||
| 504 | installSuccess += 1 | ||
| 505 | } | ||
| 506 | |||
| 507 | NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { | ||
| 508 | installOverwrite += 1 | ||
| 509 | } | ||
| 510 | |||
| 511 | NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { | ||
| 512 | errorBaseGame += 1 | ||
| 513 | } | ||
| 514 | |||
| 515 | NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { | ||
| 516 | errorExtension += 1 | ||
| 517 | } | ||
| 518 | |||
| 519 | else -> { | ||
| 520 | errorOther += 1 | ||
| 521 | } | ||
| 522 | } | 515 | } |
| 523 | } | 516 | |
| 524 | withContext(Dispatchers.Main) { | 517 | NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { |
| 525 | val separator = System.getProperty("line.separator") ?: "\n" | 518 | installOverwrite += 1 |
| 526 | val installResult = StringBuilder() | ||
| 527 | if (installSuccess > 0) { | ||
| 528 | installResult.append( | ||
| 529 | getString( | ||
| 530 | R.string.install_game_content_success_install, | ||
| 531 | installSuccess | ||
| 532 | ) | ||
| 533 | ) | ||
| 534 | installResult.append(separator) | ||
| 535 | } | 519 | } |
| 536 | if (installOverwrite > 0) { | 520 | |
| 537 | installResult.append( | 521 | NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { |
| 538 | getString( | 522 | errorBaseGame += 1 |
| 539 | R.string.install_game_content_success_overwrite, | ||
| 540 | installOverwrite | ||
| 541 | ) | ||
| 542 | ) | ||
| 543 | installResult.append(separator) | ||
| 544 | } | 523 | } |
| 545 | errorTotal = errorBaseGame + errorExtension + errorOther | 524 | |
| 546 | if (errorTotal > 0) { | 525 | NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { |
| 547 | installResult.append(separator) | 526 | errorExtension += 1 |
| 548 | installResult.append( | 527 | } |
| 549 | getString( | 528 | |
| 550 | R.string.install_game_content_failed_count, | 529 | else -> { |
| 551 | errorTotal | 530 | errorOther += 1 |
| 552 | ) | ||
| 553 | ) | ||
| 554 | installResult.append(separator) | ||
| 555 | if (errorBaseGame > 0) { | ||
| 556 | installResult.append(separator) | ||
| 557 | installResult.append( | ||
| 558 | getString(R.string.install_game_content_failure_base) | ||
| 559 | ) | ||
| 560 | installResult.append(separator) | ||
| 561 | } | ||
| 562 | if (errorExtension > 0) { | ||
| 563 | installResult.append(separator) | ||
| 564 | installResult.append( | ||
| 565 | getString(R.string.install_game_content_failure_file_extension) | ||
| 566 | ) | ||
| 567 | installResult.append(separator) | ||
| 568 | } | ||
| 569 | if (errorOther > 0) { | ||
| 570 | installResult.append( | ||
| 571 | getString(R.string.install_game_content_failure_description) | ||
| 572 | ) | ||
| 573 | installResult.append(separator) | ||
| 574 | } | ||
| 575 | LongMessageDialogFragment.newInstance( | ||
| 576 | R.string.install_game_content_failure, | ||
| 577 | installResult.toString().trim(), | ||
| 578 | R.string.install_game_content_help_link | ||
| 579 | ).show(supportFragmentManager, LongMessageDialogFragment.TAG) | ||
| 580 | } else { | ||
| 581 | LongMessageDialogFragment.newInstance( | ||
| 582 | R.string.install_game_content_success, | ||
| 583 | installResult.toString().trim() | ||
| 584 | ).show(supportFragmentManager, LongMessageDialogFragment.TAG) | ||
| 585 | } | 531 | } |
| 586 | } | 532 | } |
| 587 | } | 533 | } |
| 588 | return@newInstance installSuccess + installOverwrite + errorTotal | 534 | |
| 535 | val separator = System.getProperty("line.separator") ?: "\n" | ||
| 536 | val installResult = StringBuilder() | ||
| 537 | if (installSuccess > 0) { | ||
| 538 | installResult.append( | ||
| 539 | getString( | ||
| 540 | R.string.install_game_content_success_install, | ||
| 541 | installSuccess | ||
| 542 | ) | ||
| 543 | ) | ||
| 544 | installResult.append(separator) | ||
| 545 | } | ||
| 546 | if (installOverwrite > 0) { | ||
| 547 | installResult.append( | ||
| 548 | getString( | ||
| 549 | R.string.install_game_content_success_overwrite, | ||
| 550 | installOverwrite | ||
| 551 | ) | ||
| 552 | ) | ||
| 553 | installResult.append(separator) | ||
| 554 | } | ||
| 555 | val errorTotal: Int = errorBaseGame + errorExtension + errorOther | ||
| 556 | if (errorTotal > 0) { | ||
| 557 | installResult.append(separator) | ||
| 558 | installResult.append( | ||
| 559 | getString( | ||
| 560 | R.string.install_game_content_failed_count, | ||
| 561 | errorTotal | ||
| 562 | ) | ||
| 563 | ) | ||
| 564 | installResult.append(separator) | ||
| 565 | if (errorBaseGame > 0) { | ||
| 566 | installResult.append(separator) | ||
| 567 | installResult.append( | ||
| 568 | getString(R.string.install_game_content_failure_base) | ||
| 569 | ) | ||
| 570 | installResult.append(separator) | ||
| 571 | } | ||
| 572 | if (errorExtension > 0) { | ||
| 573 | installResult.append(separator) | ||
| 574 | installResult.append( | ||
| 575 | getString(R.string.install_game_content_failure_file_extension) | ||
| 576 | ) | ||
| 577 | installResult.append(separator) | ||
| 578 | } | ||
| 579 | if (errorOther > 0) { | ||
| 580 | installResult.append( | ||
| 581 | getString(R.string.install_game_content_failure_description) | ||
| 582 | ) | ||
| 583 | installResult.append(separator) | ||
| 584 | } | ||
| 585 | return@newInstance MessageDialogFragment.newInstance( | ||
| 586 | titleId = R.string.install_game_content_failure, | ||
| 587 | descriptionString = installResult.toString().trim(), | ||
| 588 | helpLinkId = R.string.install_game_content_help_link | ||
| 589 | ) | ||
| 590 | } else { | ||
| 591 | return@newInstance MessageDialogFragment.newInstance( | ||
| 592 | titleId = R.string.install_game_content_success, | ||
| 593 | descriptionString = installResult.toString().trim() | ||
| 594 | ) | ||
| 595 | } | ||
| 589 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 596 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) |
| 590 | } | 597 | } |
| 591 | } | 598 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt deleted file mode 100644 index 9cfda74ee..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt +++ /dev/null | |||
| @@ -1,25 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | class BiMap<K, V> { | ||
| 7 | private val forward: MutableMap<K, V> = HashMap() | ||
| 8 | private val backward: MutableMap<V, K> = HashMap() | ||
| 9 | |||
| 10 | @Synchronized | ||
| 11 | fun add(key: K, value: V) { | ||
| 12 | forward[key] = value | ||
| 13 | backward[value] = key | ||
| 14 | } | ||
| 15 | |||
| 16 | @Synchronized | ||
| 17 | fun getForward(key: K): V? { | ||
| 18 | return forward[key] | ||
| 19 | } | ||
| 20 | |||
| 21 | @Synchronized | ||
| 22 | fun getBackward(key: V): K? { | ||
| 23 | return backward[key] | ||
| 24 | } | ||
| 25 | } | ||
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 2ee63697e..3c9f6bad0 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 | |||
| @@ -3,18 +3,18 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import java.io.IOException | 6 | import java.io.IOException |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | 7 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 9 | 9 | ||
| 10 | object DirectoryInitialization { | 10 | object DirectoryInitialization { |
| 11 | private var userPath: String? = null | 11 | private var userPath: String? = null |
| 12 | 12 | ||
| 13 | var areDirectoriesReady: Boolean = false | 13 | var areDirectoriesReady: Boolean = false |
| 14 | 14 | ||
| 15 | fun start(context: Context) { | 15 | fun start() { |
| 16 | if (!areDirectoriesReady) { | 16 | if (!areDirectoriesReady) { |
| 17 | initializeInternalStorage(context) | 17 | initializeInternalStorage() |
| 18 | NativeLibrary.initializeEmulation() | 18 | NativeLibrary.initializeEmulation() |
| 19 | areDirectoriesReady = true | 19 | areDirectoriesReady = true |
| 20 | } | 20 | } |
| @@ -26,9 +26,9 @@ object DirectoryInitialization { | |||
| 26 | return userPath | 26 | return userPath |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | private fun initializeInternalStorage(context: Context) { | 29 | private fun initializeInternalStorage() { |
| 30 | try { | 30 | try { |
| 31 | userPath = context.getExternalFilesDir(null)!!.canonicalPath | 31 | userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath |
| 32 | NativeLibrary.setAppDirectory(userPath!!) | 32 | NativeLibrary.setAppDirectory(userPath!!) |
| 33 | } catch (e: IOException) { | 33 | } catch (e: IOException) { |
| 34 | e.printStackTrace() | 34 | e.printStackTrace() |
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 f8e7eeca7..e0ee29c9b 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 | |||
| @@ -11,6 +11,7 @@ import kotlinx.serialization.json.Json | |||
| 11 | import org.yuzu.yuzu_emu.NativeLibrary | 11 | import org.yuzu.yuzu_emu.NativeLibrary |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 12 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.model.Game | 13 | import org.yuzu.yuzu_emu.model.Game |
| 14 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 14 | 15 | ||
| 15 | object GameHelper { | 16 | object GameHelper { |
| 16 | const val KEY_GAME_PATH = "game_path" | 17 | const val KEY_GAME_PATH = "game_path" |
| @@ -29,15 +30,7 @@ object GameHelper { | |||
| 29 | // Ensure keys are loaded so that ROM metadata can be decrypted. | 30 | // Ensure keys are loaded so that ROM metadata can be decrypted. |
| 30 | NativeLibrary.reloadKeys() | 31 | NativeLibrary.reloadKeys() |
| 31 | 32 | ||
| 32 | val children = FileUtil.listFiles(context, gamesUri) | 33 | addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3) |
| 33 | for (file in children) { | ||
| 34 | if (!file.isDirectory) { | ||
| 35 | // Check that the file has an extension we care about before trying to read out of it. | ||
| 36 | if (Game.extensions.contains(FileUtil.getExtension(file.uri))) { | ||
| 37 | games.add(getGame(file.uri)) | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | 34 | ||
| 42 | // Cache list of games found on disk | 35 | // Cache list of games found on disk |
| 43 | val serializedGames = mutableSetOf<String>() | 36 | val serializedGames = mutableSetOf<String>() |
| @@ -52,7 +45,31 @@ object GameHelper { | |||
| 52 | return games.toList() | 45 | return games.toList() |
| 53 | } | 46 | } |
| 54 | 47 | ||
| 55 | private fun getGame(uri: Uri): Game { | 48 | private fun addGamesRecursive( |
| 49 | games: MutableList<Game>, | ||
| 50 | files: Array<MinimalDocumentFile>, | ||
| 51 | depth: Int | ||
| 52 | ) { | ||
| 53 | if (depth <= 0) { | ||
| 54 | return | ||
| 55 | } | ||
| 56 | |||
| 57 | files.forEach { | ||
| 58 | if (it.isDirectory) { | ||
| 59 | addGamesRecursive( | ||
| 60 | games, | ||
| 61 | FileUtil.listFiles(YuzuApplication.appContext, it.uri), | ||
| 62 | depth - 1 | ||
| 63 | ) | ||
| 64 | } else { | ||
| 65 | if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { | ||
| 66 | games.add(getGame(it.uri, true)) | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | fun getGame(uri: Uri, addedToLibrary: Boolean): Game { | ||
| 56 | val filePath = uri.toString() | 73 | val filePath = uri.toString() |
| 57 | var name = NativeLibrary.getTitle(filePath) | 74 | var name = NativeLibrary.getTitle(filePath) |
| 58 | 75 | ||
| @@ -77,11 +94,13 @@ object GameHelper { | |||
| 77 | NativeLibrary.isHomebrew(filePath) | 94 | NativeLibrary.isHomebrew(filePath) |
| 78 | ) | 95 | ) |
| 79 | 96 | ||
| 80 | val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) | 97 | if (addedToLibrary) { |
| 81 | if (addedTime == 0L) { | 98 | val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) |
| 82 | preferences.edit() | 99 | if (addedTime == 0L) { |
| 83 | .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis()) | 100 | preferences.edit() |
| 84 | .apply() | 101 | .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis()) |
| 102 | .apply() | ||
| 103 | } | ||
| 85 | } | 104 | } |
| 86 | 105 | ||
| 87 | return newGame | 106 | return newGame |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt new file mode 100644 index 000000000..9fe99fab1 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | import android.graphics.Bitmap | ||
| 7 | import android.graphics.BitmapFactory | ||
| 8 | import android.widget.ImageView | ||
| 9 | import androidx.core.graphics.drawable.toBitmap | ||
| 10 | import androidx.core.graphics.drawable.toDrawable | ||
| 11 | import coil.ImageLoader | ||
| 12 | import coil.decode.DataSource | ||
| 13 | import coil.executeBlocking | ||
| 14 | import coil.fetch.DrawableResult | ||
| 15 | import coil.fetch.FetchResult | ||
| 16 | import coil.fetch.Fetcher | ||
| 17 | import coil.key.Keyer | ||
| 18 | import coil.memory.MemoryCache | ||
| 19 | import coil.request.ImageRequest | ||
| 20 | import coil.request.Options | ||
| 21 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 22 | import org.yuzu.yuzu_emu.R | ||
| 23 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 24 | import org.yuzu.yuzu_emu.model.Game | ||
| 25 | |||
| 26 | class GameIconFetcher( | ||
| 27 | private val game: Game, | ||
| 28 | private val options: Options | ||
| 29 | ) : Fetcher { | ||
| 30 | override suspend fun fetch(): FetchResult { | ||
| 31 | return DrawableResult( | ||
| 32 | drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources), | ||
| 33 | isSampled = false, | ||
| 34 | dataSource = DataSource.DISK | ||
| 35 | ) | ||
| 36 | } | ||
| 37 | |||
| 38 | private fun decodeGameIcon(uri: String): Bitmap? { | ||
| 39 | val data = NativeLibrary.getIcon(uri) | ||
| 40 | return BitmapFactory.decodeByteArray( | ||
| 41 | data, | ||
| 42 | 0, | ||
| 43 | data.size, | ||
| 44 | BitmapFactory.Options() | ||
| 45 | ) | ||
| 46 | } | ||
| 47 | |||
| 48 | class Factory : Fetcher.Factory<Game> { | ||
| 49 | override fun create(data: Game, options: Options, imageLoader: ImageLoader): Fetcher = | ||
| 50 | GameIconFetcher(data, options) | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | class GameIconKeyer : Keyer<Game> { | ||
| 55 | override fun key(data: Game, options: Options): String = data.path | ||
| 56 | } | ||
| 57 | |||
| 58 | object GameIconUtils { | ||
| 59 | private val imageLoader = ImageLoader.Builder(YuzuApplication.appContext) | ||
| 60 | .components { | ||
| 61 | add(GameIconKeyer()) | ||
| 62 | add(GameIconFetcher.Factory()) | ||
| 63 | } | ||
| 64 | .memoryCache { | ||
| 65 | MemoryCache.Builder(YuzuApplication.appContext) | ||
| 66 | .maxSizePercent(0.25) | ||
| 67 | .build() | ||
| 68 | } | ||
| 69 | .build() | ||
| 70 | |||
| 71 | fun loadGameIcon(game: Game, imageView: ImageView) { | ||
| 72 | val request = ImageRequest.Builder(YuzuApplication.appContext) | ||
| 73 | .data(game) | ||
| 74 | .target(imageView) | ||
| 75 | .error(R.drawable.default_icon) | ||
| 76 | .build() | ||
| 77 | imageLoader.enqueue(request) | ||
| 78 | } | ||
| 79 | |||
| 80 | fun getGameIcon(game: Game): Bitmap { | ||
| 81 | val request = ImageRequest.Builder(YuzuApplication.appContext) | ||
| 82 | .data(game) | ||
| 83 | .error(R.drawable.default_icon) | ||
| 84 | .build() | ||
| 85 | return imageLoader.executeBlocking(request) | ||
| 86 | .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888) | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt new file mode 100644 index 000000000..9425f8b99 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | object NativeConfig { | ||
| 7 | external fun getBoolean(key: String, getDefault: Boolean): Boolean | ||
| 8 | external fun setBoolean(key: String, value: Boolean) | ||
| 9 | |||
| 10 | external fun getByte(key: String, getDefault: Boolean): Byte | ||
| 11 | external fun setByte(key: String, value: Byte) | ||
| 12 | |||
| 13 | external fun getShort(key: String, getDefault: Boolean): Short | ||
| 14 | external fun setShort(key: String, value: Short) | ||
| 15 | |||
| 16 | external fun getInt(key: String, getDefault: Boolean): Int | ||
| 17 | external fun setInt(key: String, value: Int) | ||
| 18 | |||
| 19 | external fun getFloat(key: String, getDefault: Boolean): Float | ||
| 20 | external fun setFloat(key: String, value: Float) | ||
| 21 | |||
| 22 | external fun getLong(key: String, getDefault: Boolean): Long | ||
| 23 | external fun setLong(key: String, value: Long) | ||
| 24 | |||
| 25 | external fun getString(key: String, getDefault: Boolean): String | ||
| 26 | external fun setString(key: String, value: String) | ||
| 27 | |||
| 28 | external fun getIsRuntimeModifiable(key: String): Boolean | ||
| 29 | |||
| 30 | external fun getConfigHeader(category: Int): String | ||
| 31 | |||
| 32 | external fun getPairedSettingKey(key: String): String | ||
| 33 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt new file mode 100644 index 000000000..f9a3e4126 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | import android.view.View | ||
| 7 | |||
| 8 | object ViewUtils { | ||
| 9 | fun showView(view: View, length: Long = 300) { | ||
| 10 | view.apply { | ||
| 11 | alpha = 0f | ||
| 12 | visibility = View.VISIBLE | ||
| 13 | isClickable = true | ||
| 14 | }.animate().apply { | ||
| 15 | duration = length | ||
| 16 | alpha(1f) | ||
| 17 | }.start() | ||
| 18 | } | ||
| 19 | |||
| 20 | fun hideView(view: View, length: Long = 300) { | ||
| 21 | if (view.visibility == View.INVISIBLE) { | ||
| 22 | return | ||
| 23 | } | ||
| 24 | |||
| 25 | view.apply { | ||
| 26 | alpha = 1f | ||
| 27 | isClickable = false | ||
| 28 | }.animate().apply { | ||
| 29 | duration = length | ||
| 30 | alpha(0f) | ||
| 31 | }.withEndAction { | ||
| 32 | view.visibility = View.INVISIBLE | ||
| 33 | }.start() | ||
| 34 | } | ||
| 35 | } | ||
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 685ccaa76..2f0868c63 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 | |||
| @@ -7,7 +7,6 @@ import android.content.Context | |||
| 7 | import android.util.AttributeSet | 7 | import android.util.AttributeSet |
| 8 | import android.util.Rational | 8 | import android.util.Rational |
| 9 | import android.view.SurfaceView | 9 | import android.view.SurfaceView |
| 10 | import kotlin.math.roundToInt | ||
| 11 | 10 | ||
| 12 | class FixedRatioSurfaceView @JvmOverloads constructor( | 11 | class FixedRatioSurfaceView @JvmOverloads constructor( |
| 13 | context: Context, | 12 | context: Context, |
| @@ -22,27 +21,44 @@ class FixedRatioSurfaceView @JvmOverloads constructor( | |||
| 22 | */ | 21 | */ |
| 23 | fun setAspectRatio(ratio: Rational?) { | 22 | fun setAspectRatio(ratio: Rational?) { |
| 24 | aspectRatio = ratio?.toFloat() ?: 0f | 23 | aspectRatio = ratio?.toFloat() ?: 0f |
| 24 | requestLayout() | ||
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | 27 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { |
| 28 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) | 28 | val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat() |
| 29 | val width = MeasureSpec.getSize(widthMeasureSpec) | 29 | val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat() |
| 30 | val height = MeasureSpec.getSize(heightMeasureSpec) | ||
| 31 | if (aspectRatio != 0f) { | 30 | if (aspectRatio != 0f) { |
| 32 | val newWidth: Int | 31 | val displayAspect = displayWidth / displayHeight |
| 33 | val newHeight: Int | 32 | if (displayAspect < aspectRatio) { |
| 34 | if (height * aspectRatio < width) { | 33 | // Max out width |
| 35 | newWidth = (height * aspectRatio).roundToInt() | 34 | val halfHeight = displayHeight / 2 |
| 36 | newHeight = height | 35 | val surfaceHeight = displayWidth / aspectRatio |
| 36 | val newTop: Float = halfHeight - (surfaceHeight / 2) | ||
| 37 | val newBottom: Float = halfHeight + (surfaceHeight / 2) | ||
| 38 | super.onMeasure( | ||
| 39 | widthMeasureSpec, | ||
| 40 | MeasureSpec.makeMeasureSpec( | ||
| 41 | newBottom.toInt() - newTop.toInt(), | ||
| 42 | MeasureSpec.EXACTLY | ||
| 43 | ) | ||
| 44 | ) | ||
| 45 | return | ||
| 37 | } else { | 46 | } else { |
| 38 | newWidth = width | 47 | // Max out height |
| 39 | newHeight = (width / aspectRatio).roundToInt() | 48 | val halfWidth = displayWidth / 2 |
| 49 | val surfaceWidth = displayHeight * aspectRatio | ||
| 50 | val newLeft: Float = halfWidth - (surfaceWidth / 2) | ||
| 51 | val newRight: Float = halfWidth + (surfaceWidth / 2) | ||
| 52 | super.onMeasure( | ||
| 53 | MeasureSpec.makeMeasureSpec( | ||
| 54 | newRight.toInt() - newLeft.toInt(), | ||
| 55 | MeasureSpec.EXACTLY | ||
| 56 | ), | ||
| 57 | heightMeasureSpec | ||
| 58 | ) | ||
| 59 | return | ||
| 40 | } | 60 | } |
| 41 | val left = (width - newWidth) / 2 | ||
| 42 | val top = (height - newHeight) / 2 | ||
| 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) | ||
| 44 | } else { | ||
| 45 | setLeftTopRightBottom(0, 0, width, height) | ||
| 46 | } | 61 | } |
| 62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) | ||
| 47 | } | 63 | } |
| 48 | } | 64 | } |
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index e2ed08e9f..e15d1480b 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt | |||
| @@ -14,6 +14,8 @@ add_library(yuzu-android SHARED | |||
| 14 | id_cache.cpp | 14 | id_cache.cpp |
| 15 | id_cache.h | 15 | id_cache.h |
| 16 | native.cpp | 16 | native.cpp |
| 17 | native_config.cpp | ||
| 18 | uisettings.cpp | ||
| 17 | ) | 19 | ) |
| 18 | 20 | ||
| 19 | set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) | 21 | set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) |
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 5e1f10f99..81120ab0f 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp | |||
| @@ -11,22 +11,25 @@ | |||
| 11 | #include "common/fs/path_util.h" | 11 | #include "common/fs/path_util.h" |
| 12 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 13 | #include "common/settings.h" | 13 | #include "common/settings.h" |
| 14 | #include "common/settings_enums.h" | ||
| 14 | #include "core/hle/service/acc/profile_manager.h" | 15 | #include "core/hle/service/acc/profile_manager.h" |
| 15 | #include "input_common/main.h" | 16 | #include "input_common/main.h" |
| 16 | #include "jni/config.h" | 17 | #include "jni/config.h" |
| 17 | #include "jni/default_ini.h" | 18 | #include "jni/default_ini.h" |
| 19 | #include "uisettings.h" | ||
| 18 | 20 | ||
| 19 | namespace FS = Common::FS; | 21 | namespace FS = Common::FS; |
| 20 | 22 | ||
| 21 | Config::Config(std::optional<std::filesystem::path> config_path) | 23 | Config::Config(const std::string& config_name, ConfigType config_type) |
| 22 | : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")}, | 24 | : type(config_type), global{config_type == ConfigType::GlobalConfig} { |
| 23 | config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} { | 25 | Initialize(config_name); |
| 24 | Reload(); | ||
| 25 | } | 26 | } |
| 26 | 27 | ||
| 27 | Config::~Config() = default; | 28 | Config::~Config() = default; |
| 28 | 29 | ||
| 29 | bool Config::LoadINI(const std::string& default_contents, bool retry) { | 30 | bool Config::LoadINI(const std::string& default_contents, bool retry) { |
| 31 | void(FS::CreateParentDir(config_loc)); | ||
| 32 | config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc)); | ||
| 30 | const auto config_loc_str = FS::PathToUTF8String(config_loc); | 33 | const auto config_loc_str = FS::PathToUTF8String(config_loc); |
| 31 | if (config->ParseError() < 0) { | 34 | if (config->ParseError() < 0) { |
| 32 | if (retry) { | 35 | if (retry) { |
| @@ -144,7 +147,9 @@ void Config::ReadValues() { | |||
| 144 | Service::Account::MAX_USERS - 1); | 147 | Service::Account::MAX_USERS - 1); |
| 145 | 148 | ||
| 146 | // Disable docked mode by default on Android | 149 | // Disable docked mode by default on Android |
| 147 | Settings::values.use_docked_mode = config->GetBoolean("System", "use_docked_mode", false); | 150 | Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false) |
| 151 | ? Settings::ConsoleMode::Docked | ||
| 152 | : Settings::ConsoleMode::Handheld); | ||
| 148 | 153 | ||
| 149 | const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); | 154 | const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); |
| 150 | if (rng_seed_enabled) { | 155 | if (rng_seed_enabled) { |
| @@ -277,7 +282,7 @@ void Config::ReadValues() { | |||
| 277 | std::stringstream ss(title_list); | 282 | std::stringstream ss(title_list); |
| 278 | std::string line; | 283 | std::string line; |
| 279 | while (std::getline(ss, line, '|')) { | 284 | while (std::getline(ss, line, '|')) { |
| 280 | const auto title_id = std::stoul(line, nullptr, 16); | 285 | const auto title_id = std::strtoul(line.c_str(), nullptr, 16); |
| 281 | const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); | 286 | const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); |
| 282 | 287 | ||
| 283 | std::stringstream inner_ss(disabled_list); | 288 | std::stringstream inner_ss(disabled_list); |
| @@ -298,9 +303,28 @@ void Config::ReadValues() { | |||
| 298 | 303 | ||
| 299 | // Network | 304 | // Network |
| 300 | ReadSetting("Network", Settings::values.network_interface); | 305 | ReadSetting("Network", Settings::values.network_interface); |
| 306 | |||
| 307 | // Android | ||
| 308 | ReadSetting("Android", AndroidSettings::values.picture_in_picture); | ||
| 309 | ReadSetting("Android", AndroidSettings::values.screen_layout); | ||
| 301 | } | 310 | } |
| 302 | 311 | ||
| 303 | void Config::Reload() { | 312 | void Config::Initialize(const std::string& config_name) { |
| 313 | const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); | ||
| 314 | const auto config_file = fmt::format("{}.ini", config_name); | ||
| 315 | |||
| 316 | switch (type) { | ||
| 317 | case ConfigType::GlobalConfig: | ||
| 318 | config_loc = FS::PathToUTF8String(fs_config_loc / config_file); | ||
| 319 | break; | ||
| 320 | case ConfigType::PerGameConfig: | ||
| 321 | config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); | ||
| 322 | break; | ||
| 323 | case ConfigType::InputProfile: | ||
| 324 | config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); | ||
| 325 | LoadINI(DefaultINI::android_config_file); | ||
| 326 | return; | ||
| 327 | } | ||
| 304 | LoadINI(DefaultINI::android_config_file); | 328 | LoadINI(DefaultINI::android_config_file); |
| 305 | ReadValues(); | 329 | ReadValues(); |
| 306 | } | 330 | } |
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h index 0d7d6e94d..e1e8f47ed 100644 --- a/src/android/app/src/main/jni/config.h +++ b/src/android/app/src/main/jni/config.h | |||
| @@ -13,25 +13,35 @@ | |||
| 13 | class INIReader; | 13 | class INIReader; |
| 14 | 14 | ||
| 15 | class Config { | 15 | class Config { |
| 16 | std::filesystem::path config_loc; | ||
| 17 | std::unique_ptr<INIReader> config; | ||
| 18 | |||
| 19 | bool LoadINI(const std::string& default_contents = "", bool retry = true); | 16 | bool LoadINI(const std::string& default_contents = "", bool retry = true); |
| 20 | void ReadValues(); | ||
| 21 | 17 | ||
| 22 | public: | 18 | public: |
| 23 | explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt); | 19 | enum class ConfigType { |
| 20 | GlobalConfig, | ||
| 21 | PerGameConfig, | ||
| 22 | InputProfile, | ||
| 23 | }; | ||
| 24 | |||
| 25 | explicit Config(const std::string& config_name = "config", | ||
| 26 | ConfigType config_type = ConfigType::GlobalConfig); | ||
| 24 | ~Config(); | 27 | ~Config(); |
| 25 | 28 | ||
| 26 | void Reload(); | 29 | void Initialize(const std::string& config_name); |
| 27 | 30 | ||
| 28 | private: | 31 | private: |
| 29 | /** | 32 | /** |
| 30 | * Applies a value read from the sdl2_config to a Setting. | 33 | * Applies a value read from the config to a Setting. |
| 31 | * | 34 | * |
| 32 | * @param group The name of the INI group | 35 | * @param group The name of the INI group |
| 33 | * @param setting The yuzu setting to modify | 36 | * @param setting The yuzu setting to modify |
| 34 | */ | 37 | */ |
| 35 | template <typename Type, bool ranged> | 38 | template <typename Type, bool ranged> |
| 36 | void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); | 39 | void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); |
| 40 | |||
| 41 | void ReadValues(); | ||
| 42 | |||
| 43 | const ConfigType type; | ||
| 44 | std::unique_ptr<INIReader> config; | ||
| 45 | std::string config_loc; | ||
| 46 | const bool global; | ||
| 37 | }; | 47 | }; |
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 9cbbf23a3..960abf95a 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp | |||
| @@ -15,6 +15,8 @@ static jclass s_disk_cache_progress_class; | |||
| 15 | static jclass s_load_callback_stage_class; | 15 | static jclass s_load_callback_stage_class; |
| 16 | static jmethodID s_exit_emulation_activity; | 16 | static jmethodID s_exit_emulation_activity; |
| 17 | static jmethodID s_disk_cache_load_progress; | 17 | static jmethodID s_disk_cache_load_progress; |
| 18 | static jmethodID s_on_emulation_started; | ||
| 19 | static jmethodID s_on_emulation_stopped; | ||
| 18 | 20 | ||
| 19 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | 21 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; |
| 20 | 22 | ||
| @@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() { | |||
| 59 | return s_disk_cache_load_progress; | 61 | return s_disk_cache_load_progress; |
| 60 | } | 62 | } |
| 61 | 63 | ||
| 64 | jmethodID GetOnEmulationStarted() { | ||
| 65 | return s_on_emulation_started; | ||
| 66 | } | ||
| 67 | |||
| 68 | jmethodID GetOnEmulationStopped() { | ||
| 69 | return s_on_emulation_stopped; | ||
| 70 | } | ||
| 71 | |||
| 62 | } // namespace IDCache | 72 | } // namespace IDCache |
| 63 | 73 | ||
| 64 | #ifdef __cplusplus | 74 | #ifdef __cplusplus |
| @@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |||
| 85 | env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); | 95 | env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); |
| 86 | s_disk_cache_load_progress = | 96 | s_disk_cache_load_progress = |
| 87 | env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); | 97 | env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); |
| 98 | s_on_emulation_started = | ||
| 99 | env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); | ||
| 100 | s_on_emulation_stopped = | ||
| 101 | env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); | ||
| 88 | 102 | ||
| 89 | // Initialize Android Storage | 103 | // Initialize Android Storage |
| 90 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | 104 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); |
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index be535fe1e..b76158928 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h | |||
| @@ -15,5 +15,7 @@ jclass GetDiskCacheProgressClass(); | |||
| 15 | jclass GetDiskCacheLoadCallbackStageClass(); | 15 | jclass GetDiskCacheLoadCallbackStageClass(); |
| 16 | jmethodID GetExitEmulationActivity(); | 16 | jmethodID GetExitEmulationActivity(); |
| 17 | jmethodID GetDiskCacheLoadProgress(); | 17 | jmethodID GetDiskCacheLoadProgress(); |
| 18 | jmethodID GetOnEmulationStarted(); | ||
| 19 | jmethodID GetOnEmulationStopped(); | ||
| 18 | 20 | ||
| 19 | } // namespace IDCache | 21 | } // namespace IDCache |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index c23b2f19e..f31fe054b 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -30,6 +30,7 @@ | |||
| 30 | #include "core/cpu_manager.h" | 30 | #include "core/cpu_manager.h" |
| 31 | #include "core/crypto/key_manager.h" | 31 | #include "core/crypto/key_manager.h" |
| 32 | #include "core/file_sys/card_image.h" | 32 | #include "core/file_sys/card_image.h" |
| 33 | #include "core/file_sys/content_archive.h" | ||
| 33 | #include "core/file_sys/registered_cache.h" | 34 | #include "core/file_sys/registered_cache.h" |
| 34 | #include "core/file_sys/submission_package.h" | 35 | #include "core/file_sys/submission_package.h" |
| 35 | #include "core/file_sys/vfs.h" | 36 | #include "core/file_sys/vfs.h" |
| @@ -202,12 +203,10 @@ public: | |||
| 202 | } | 203 | } |
| 203 | 204 | ||
| 204 | bool IsRunning() const { | 205 | bool IsRunning() const { |
| 205 | std::scoped_lock lock(m_mutex); | ||
| 206 | return m_is_running; | 206 | return m_is_running; |
| 207 | } | 207 | } |
| 208 | 208 | ||
| 209 | bool IsPaused() const { | 209 | bool IsPaused() const { |
| 210 | std::scoped_lock lock(m_mutex); | ||
| 211 | return m_is_running && m_is_paused; | 210 | return m_is_running && m_is_paused; |
| 212 | } | 211 | } |
| 213 | 212 | ||
| @@ -224,17 +223,51 @@ public: | |||
| 224 | m_system.Renderer().NotifySurfaceChanged(); | 223 | m_system.Renderer().NotifySurfaceChanged(); |
| 225 | } | 224 | } |
| 226 | 225 | ||
| 226 | void ConfigureFilesystemProvider(const std::string& filepath) { | ||
| 227 | const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); | ||
| 228 | if (!file) { | ||
| 229 | return; | ||
| 230 | } | ||
| 231 | |||
| 232 | auto loader = Loader::GetLoader(m_system, file); | ||
| 233 | if (!loader) { | ||
| 234 | return; | ||
| 235 | } | ||
| 236 | |||
| 237 | const auto file_type = loader->GetFileType(); | ||
| 238 | if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { | ||
| 239 | return; | ||
| 240 | } | ||
| 241 | |||
| 242 | u64 program_id = 0; | ||
| 243 | const auto res2 = loader->ReadProgramId(program_id); | ||
| 244 | if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { | ||
| 245 | m_manual_provider->AddEntry(FileSys::TitleType::Application, | ||
| 246 | FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), | ||
| 247 | program_id, file); | ||
| 248 | } else if (res2 == Loader::ResultStatus::Success && | ||
| 249 | (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { | ||
| 250 | const auto nsp = file_type == Loader::FileType::NSP | ||
| 251 | ? std::make_shared<FileSys::NSP>(file) | ||
| 252 | : FileSys::XCI{file}.GetSecurePartitionNSP(); | ||
| 253 | for (const auto& title : nsp->GetNCAs()) { | ||
| 254 | for (const auto& entry : title.second) { | ||
| 255 | m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, | ||
| 256 | entry.second->GetBaseFile()); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 227 | Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { | 262 | Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { |
| 228 | std::scoped_lock lock(m_mutex); | 263 | std::scoped_lock lock(m_mutex); |
| 229 | 264 | ||
| 230 | // Loads the configuration. | ||
| 231 | Config{}; | ||
| 232 | |||
| 233 | // Create the render window. | 265 | // Create the render window. |
| 234 | m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, | 266 | m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, |
| 235 | m_vulkan_library); | 267 | m_vulkan_library); |
| 236 | 268 | ||
| 237 | m_system.SetFilesystem(m_vfs); | 269 | m_system.SetFilesystem(m_vfs); |
| 270 | m_system.GetUserChannel().clear(); | ||
| 238 | 271 | ||
| 239 | // Initialize system. | 272 | // Initialize system. |
| 240 | jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); | 273 | jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); |
| @@ -254,8 +287,14 @@ public: | |||
| 254 | std::move(android_keyboard), // Software Keyboard | 287 | std::move(android_keyboard), // Software Keyboard |
| 255 | nullptr, // Web Browser | 288 | nullptr, // Web Browser |
| 256 | }); | 289 | }); |
| 290 | |||
| 291 | // Initialize filesystem. | ||
| 292 | m_manual_provider = std::make_unique<FileSys::ManualContentProvider>(); | ||
| 257 | m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); | 293 | m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |
| 294 | m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, | ||
| 295 | m_manual_provider.get()); | ||
| 258 | m_system.GetFileSystemController().CreateFactories(*m_vfs); | 296 | m_system.GetFileSystemController().CreateFactories(*m_vfs); |
| 297 | ConfigureFilesystemProvider(filepath); | ||
| 259 | 298 | ||
| 260 | // Initialize account manager | 299 | // Initialize account manager |
| 261 | m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); | 300 | m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); |
| @@ -288,6 +327,9 @@ public: | |||
| 288 | m_system.ShutdownMainProcess(); | 327 | m_system.ShutdownMainProcess(); |
| 289 | m_detached_tasks.WaitForAllTasks(); | 328 | m_detached_tasks.WaitForAllTasks(); |
| 290 | m_load_result = Core::SystemResultStatus::ErrorNotInitialized; | 329 | m_load_result = Core::SystemResultStatus::ErrorNotInitialized; |
| 330 | m_window.reset(); | ||
| 331 | OnEmulationStopped(Core::SystemResultStatus::Success); | ||
| 332 | return; | ||
| 291 | } | 333 | } |
| 292 | 334 | ||
| 293 | // Tear down the render window. | 335 | // Tear down the render window. |
| @@ -333,6 +375,8 @@ public: | |||
| 333 | m_system.InitializeDebugger(); | 375 | m_system.InitializeDebugger(); |
| 334 | } | 376 | } |
| 335 | 377 | ||
| 378 | OnEmulationStarted(); | ||
| 379 | |||
| 336 | while (true) { | 380 | while (true) { |
| 337 | { | 381 | { |
| 338 | [[maybe_unused]] std::unique_lock lock(m_mutex); | 382 | [[maybe_unused]] std::unique_lock lock(m_mutex); |
| @@ -377,7 +421,7 @@ public: | |||
| 377 | return false; | 421 | return false; |
| 378 | } | 422 | } |
| 379 | 423 | ||
| 380 | return !Settings::values.use_docked_mode.GetValue(); | 424 | return !Settings::IsDockedMode(); |
| 381 | } | 425 | } |
| 382 | 426 | ||
| 383 | void SetDeviceType([[maybe_unused]] int index, int type) { | 427 | void SetDeviceType([[maybe_unused]] int index, int type) { |
| @@ -468,6 +512,18 @@ private: | |||
| 468 | static_cast<jint>(progress), static_cast<jint>(max)); | 512 | static_cast<jint>(progress), static_cast<jint>(max)); |
| 469 | } | 513 | } |
| 470 | 514 | ||
| 515 | static void OnEmulationStarted() { | ||
| 516 | JNIEnv* env = IDCache::GetEnvForThread(); | ||
| 517 | env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), | ||
| 518 | IDCache::GetOnEmulationStarted()); | ||
| 519 | } | ||
| 520 | |||
| 521 | static void OnEmulationStopped(Core::SystemResultStatus result) { | ||
| 522 | JNIEnv* env = IDCache::GetEnvForThread(); | ||
| 523 | env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), | ||
| 524 | IDCache::GetOnEmulationStopped(), static_cast<jint>(result)); | ||
| 525 | } | ||
| 526 | |||
| 471 | private: | 527 | private: |
| 472 | static EmulationSession s_instance; | 528 | static EmulationSession s_instance; |
| 473 | 529 | ||
| @@ -485,10 +541,11 @@ private: | |||
| 485 | Core::PerfStatsResults m_perf_stats{}; | 541 | Core::PerfStatsResults m_perf_stats{}; |
| 486 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; | 542 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; |
| 487 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; | 543 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; |
| 488 | bool m_is_running{}; | 544 | std::atomic<bool> m_is_running = false; |
| 489 | bool m_is_paused{}; | 545 | std::atomic<bool> m_is_paused = false; |
| 490 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; | 546 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; |
| 491 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; | 547 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; |
| 548 | std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; | ||
| 492 | 549 | ||
| 493 | // GPU driver parameters | 550 | // GPU driver parameters |
| 494 | std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; | 551 | std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; |
| @@ -613,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz | |||
| 613 | return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); | 670 | return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); |
| 614 | } | 671 | } |
| 615 | 672 | ||
| 616 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) { | ||
| 617 | Settings::values.audio_muted = true; | ||
| 618 | } | ||
| 619 | |||
| 620 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) { | ||
| 621 | Settings::values.audio_muted = false; | ||
| 622 | } | ||
| 623 | |||
| 624 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) { | ||
| 625 | return static_cast<jboolean>(Settings::values.audio_muted.GetValue()); | ||
| 626 | } | ||
| 627 | |||
| 628 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { | 673 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { |
| 629 | return EmulationSession::GetInstance().IsHandheldOnly(); | 674 | return EmulationSession::GetInstance().IsHandheldOnly(); |
| 630 | } | 675 | } |
| @@ -780,34 +825,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl | |||
| 780 | Config{}; | 825 | Config{}; |
| 781 | } | 826 | } |
| 782 | 827 | ||
| 783 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz, | ||
| 784 | jstring j_game_id, jstring j_section, | ||
| 785 | jstring j_key) { | ||
| 786 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); | ||
| 787 | std::string_view section = env->GetStringUTFChars(j_section, 0); | ||
| 788 | std::string_view key = env->GetStringUTFChars(j_key, 0); | ||
| 789 | |||
| 790 | env->ReleaseStringUTFChars(j_game_id, game_id.data()); | ||
| 791 | env->ReleaseStringUTFChars(j_section, section.data()); | ||
| 792 | env->ReleaseStringUTFChars(j_key, key.data()); | ||
| 793 | |||
| 794 | return env->NewStringUTF(""); | ||
| 795 | } | ||
| 796 | |||
| 797 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz, | ||
| 798 | jstring j_game_id, jstring j_section, | ||
| 799 | jstring j_key, jstring j_value) { | ||
| 800 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); | ||
| 801 | std::string_view section = env->GetStringUTFChars(j_section, 0); | ||
| 802 | std::string_view key = env->GetStringUTFChars(j_key, 0); | ||
| 803 | std::string_view value = env->GetStringUTFChars(j_value, 0); | ||
| 804 | |||
| 805 | env->ReleaseStringUTFChars(j_game_id, game_id.data()); | ||
| 806 | env->ReleaseStringUTFChars(j_section, section.data()); | ||
| 807 | env->ReleaseStringUTFChars(j_key, key.data()); | ||
| 808 | env->ReleaseStringUTFChars(j_value, value.data()); | ||
| 809 | } | ||
| 810 | |||
| 811 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, | 828 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, |
| 812 | jstring j_game_id) { | 829 | jstring j_game_id) { |
| 813 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); | 830 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); |
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp new file mode 100644 index 000000000..8a704960c --- /dev/null +++ b/src/android/app/src/main/jni/native_config.cpp | |||
| @@ -0,0 +1,237 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <string> | ||
| 5 | |||
| 6 | #include <jni.h> | ||
| 7 | |||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "jni/android_common/android_common.h" | ||
| 11 | #include "jni/config.h" | ||
| 12 | #include "uisettings.h" | ||
| 13 | |||
| 14 | template <typename T> | ||
| 15 | Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) { | ||
| 16 | auto key = GetJString(env, jkey); | ||
| 17 | auto basicSetting = Settings::values.linkage.by_key[key]; | ||
| 18 | auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key]; | ||
| 19 | if (basicSetting != 0) { | ||
| 20 | return static_cast<Settings::Setting<T>*>(basicSetting); | ||
| 21 | } | ||
| 22 | if (basicAndroidSetting != 0) { | ||
| 23 | return static_cast<Settings::Setting<T>*>(basicAndroidSetting); | ||
| 24 | } | ||
| 25 | LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); | ||
| 26 | return nullptr; | ||
| 27 | } | ||
| 28 | |||
| 29 | extern "C" { | ||
| 30 | |||
| 31 | jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj, | ||
| 32 | jstring jkey, jboolean getDefault) { | ||
| 33 | auto setting = getSetting<bool>(env, jkey); | ||
| 34 | if (setting == nullptr) { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | setting->SetGlobal(true); | ||
| 38 | |||
| 39 | if (static_cast<bool>(getDefault)) { | ||
| 40 | return setting->GetDefault(); | ||
| 41 | } | ||
| 42 | |||
| 43 | return setting->GetValue(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey, | ||
| 47 | jboolean value) { | ||
| 48 | auto setting = getSetting<bool>(env, jkey); | ||
| 49 | if (setting == nullptr) { | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | setting->SetGlobal(true); | ||
| 53 | setting->SetValue(static_cast<bool>(value)); | ||
| 54 | } | ||
| 55 | |||
| 56 | jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey, | ||
| 57 | jboolean getDefault) { | ||
| 58 | auto setting = getSetting<u8>(env, jkey); | ||
| 59 | if (setting == nullptr) { | ||
| 60 | return -1; | ||
| 61 | } | ||
| 62 | setting->SetGlobal(true); | ||
| 63 | |||
| 64 | if (static_cast<bool>(getDefault)) { | ||
| 65 | return setting->GetDefault(); | ||
| 66 | } | ||
| 67 | |||
| 68 | return setting->GetValue(); | ||
| 69 | } | ||
| 70 | |||
| 71 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey, | ||
| 72 | jbyte value) { | ||
| 73 | auto setting = getSetting<u8>(env, jkey); | ||
| 74 | if (setting == nullptr) { | ||
| 75 | return; | ||
| 76 | } | ||
| 77 | setting->SetGlobal(true); | ||
| 78 | setting->SetValue(value); | ||
| 79 | } | ||
| 80 | |||
| 81 | jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey, | ||
| 82 | jboolean getDefault) { | ||
| 83 | auto setting = getSetting<u16>(env, jkey); | ||
| 84 | if (setting == nullptr) { | ||
| 85 | return -1; | ||
| 86 | } | ||
| 87 | setting->SetGlobal(true); | ||
| 88 | |||
| 89 | if (static_cast<bool>(getDefault)) { | ||
| 90 | return setting->GetDefault(); | ||
| 91 | } | ||
| 92 | |||
| 93 | return setting->GetValue(); | ||
| 94 | } | ||
| 95 | |||
| 96 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey, | ||
| 97 | jshort value) { | ||
| 98 | auto setting = getSetting<u16>(env, jkey); | ||
| 99 | if (setting == nullptr) { | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | setting->SetGlobal(true); | ||
| 103 | setting->SetValue(value); | ||
| 104 | } | ||
| 105 | |||
| 106 | jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, | ||
| 107 | jboolean getDefault) { | ||
| 108 | auto setting = getSetting<int>(env, jkey); | ||
| 109 | if (setting == nullptr) { | ||
| 110 | return -1; | ||
| 111 | } | ||
| 112 | setting->SetGlobal(true); | ||
| 113 | |||
| 114 | if (static_cast<bool>(getDefault)) { | ||
| 115 | return setting->GetDefault(); | ||
| 116 | } | ||
| 117 | |||
| 118 | return setting->GetValue(); | ||
| 119 | } | ||
| 120 | |||
| 121 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey, | ||
| 122 | jint value) { | ||
| 123 | auto setting = getSetting<int>(env, jkey); | ||
| 124 | if (setting == nullptr) { | ||
| 125 | return; | ||
| 126 | } | ||
| 127 | setting->SetGlobal(true); | ||
| 128 | setting->SetValue(value); | ||
| 129 | } | ||
| 130 | |||
| 131 | jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey, | ||
| 132 | jboolean getDefault) { | ||
| 133 | auto setting = getSetting<float>(env, jkey); | ||
| 134 | if (setting == nullptr) { | ||
| 135 | return -1; | ||
| 136 | } | ||
| 137 | setting->SetGlobal(true); | ||
| 138 | |||
| 139 | if (static_cast<bool>(getDefault)) { | ||
| 140 | return setting->GetDefault(); | ||
| 141 | } | ||
| 142 | |||
| 143 | return setting->GetValue(); | ||
| 144 | } | ||
| 145 | |||
| 146 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey, | ||
| 147 | jfloat value) { | ||
| 148 | auto setting = getSetting<float>(env, jkey); | ||
| 149 | if (setting == nullptr) { | ||
| 150 | return; | ||
| 151 | } | ||
| 152 | setting->SetGlobal(true); | ||
| 153 | setting->SetValue(value); | ||
| 154 | } | ||
| 155 | |||
| 156 | jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey, | ||
| 157 | jboolean getDefault) { | ||
| 158 | auto setting = getSetting<long>(env, jkey); | ||
| 159 | if (setting == nullptr) { | ||
| 160 | return -1; | ||
| 161 | } | ||
| 162 | setting->SetGlobal(true); | ||
| 163 | |||
| 164 | if (static_cast<bool>(getDefault)) { | ||
| 165 | return setting->GetDefault(); | ||
| 166 | } | ||
| 167 | |||
| 168 | return setting->GetValue(); | ||
| 169 | } | ||
| 170 | |||
| 171 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey, | ||
| 172 | jlong value) { | ||
| 173 | auto setting = getSetting<long>(env, jkey); | ||
| 174 | if (setting == nullptr) { | ||
| 175 | return; | ||
| 176 | } | ||
| 177 | setting->SetGlobal(true); | ||
| 178 | setting->SetValue(value); | ||
| 179 | } | ||
| 180 | |||
| 181 | jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey, | ||
| 182 | jboolean getDefault) { | ||
| 183 | auto setting = getSetting<std::string>(env, jkey); | ||
| 184 | if (setting == nullptr) { | ||
| 185 | return ToJString(env, ""); | ||
| 186 | } | ||
| 187 | setting->SetGlobal(true); | ||
| 188 | |||
| 189 | if (static_cast<bool>(getDefault)) { | ||
| 190 | return ToJString(env, setting->GetDefault()); | ||
| 191 | } | ||
| 192 | |||
| 193 | return ToJString(env, setting->GetValue()); | ||
| 194 | } | ||
| 195 | |||
| 196 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey, | ||
| 197 | jstring value) { | ||
| 198 | auto setting = getSetting<std::string>(env, jkey); | ||
| 199 | if (setting == nullptr) { | ||
| 200 | return; | ||
| 201 | } | ||
| 202 | |||
| 203 | setting->SetGlobal(true); | ||
| 204 | setting->SetValue(GetJString(env, value)); | ||
| 205 | } | ||
| 206 | |||
| 207 | jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj, | ||
| 208 | jstring jkey) { | ||
| 209 | auto key = GetJString(env, jkey); | ||
| 210 | auto setting = Settings::values.linkage.by_key[key]; | ||
| 211 | if (setting != 0) { | ||
| 212 | return setting->RuntimeModfiable(); | ||
| 213 | } | ||
| 214 | LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); | ||
| 215 | return true; | ||
| 216 | } | ||
| 217 | |||
| 218 | jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj, | ||
| 219 | jint jcategory) { | ||
| 220 | auto category = static_cast<Settings::Category>(jcategory); | ||
| 221 | return ToJString(env, Settings::TranslateCategory(category)); | ||
| 222 | } | ||
| 223 | |||
| 224 | jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj, | ||
| 225 | jstring jkey) { | ||
| 226 | auto setting = getSetting<std::string>(env, jkey); | ||
| 227 | if (setting == nullptr) { | ||
| 228 | return ToJString(env, ""); | ||
| 229 | } | ||
| 230 | if (setting->PairedSetting() == nullptr) { | ||
| 231 | return ToJString(env, ""); | ||
| 232 | } | ||
| 233 | |||
| 234 | return ToJString(env, setting->PairedSetting()->GetLabel()); | ||
| 235 | } | ||
| 236 | |||
| 237 | } // extern "C" | ||
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp new file mode 100644 index 000000000..f2f0bad50 --- /dev/null +++ b/src/android/app/src/main/jni/uisettings.cpp | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "uisettings.h" | ||
| 5 | |||
| 6 | namespace AndroidSettings { | ||
| 7 | |||
| 8 | Values values; | ||
| 9 | |||
| 10 | } // namespace AndroidSettings | ||
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h new file mode 100644 index 000000000..494654af7 --- /dev/null +++ b/src/android/app/src/main/jni/uisettings.h | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <common/settings_common.h> | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/settings_setting.h" | ||
| 9 | |||
| 10 | namespace AndroidSettings { | ||
| 11 | |||
| 12 | struct Values { | ||
| 13 | Settings::Linkage linkage; | ||
| 14 | |||
| 15 | // Android | ||
| 16 | Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture", | ||
| 17 | Settings::Category::Android}; | ||
| 18 | Settings::Setting<s32> screen_layout{linkage, | ||
| 19 | 5, | ||
| 20 | "screen_layout", | ||
| 21 | Settings::Category::Android, | ||
| 22 | Settings::Specialization::Default, | ||
| 23 | true, | ||
| 24 | true}; | ||
| 25 | }; | ||
| 26 | |||
| 27 | extern Values values; | ||
| 28 | |||
| 29 | } // namespace AndroidSettings | ||
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml deleted file mode 100644 index 9f49c133a..000000000 --- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="125" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="1" | ||
| 8 | android:toAlpha="0" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="125" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="0" | ||
| 14 | android:toXDelta="-75" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml deleted file mode 100644 index 82fd719db..000000000 --- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="@android:integer/config_shortAnimTime" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="0" | ||
| 8 | android:toAlpha="1" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="@android:integer/config_shortAnimTime" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="-200" | ||
| 14 | android:toXDelta="0" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml deleted file mode 100644 index 5892128f1..000000000 --- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="125" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="1" | ||
| 8 | android:toAlpha="0" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="125" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="0" | ||
| 14 | android:toXDelta="75" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml deleted file mode 100644 index 98e0cf8bd..000000000 --- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="@android:integer/config_shortAnimTime" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="0" | ||
| 8 | android:toAlpha="1" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="@android:integer/config_shortAnimTime" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="200" | ||
| 14 | android:toXDelta="0" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml deleted file mode 100644 index 77a40a4d1..000000000 --- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="@android:integer/config_shortAnimTime" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="1" | ||
| 8 | android:toAlpha="0" /> | ||
| 9 | |||
| 10 | </set> | ||
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml deleted file mode 100644 index 4612aee13..000000000 --- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <objectAnimator | ||
| 5 | android:propertyName="translationX" | ||
| 6 | android:valueType="floatType" | ||
| 7 | android:valueFrom="-1280dp" | ||
| 8 | android:valueTo="0" | ||
| 9 | android:interpolator="@android:interpolator/decelerate_quad" | ||
| 10 | android:duration="300"/> | ||
| 11 | |||
| 12 | <objectAnimator | ||
| 13 | android:propertyName="alpha" | ||
| 14 | android:valueType="floatType" | ||
| 15 | android:valueFrom="0" | ||
| 16 | android:valueTo="1" | ||
| 17 | android:interpolator="@android:interpolator/accelerate_quad" | ||
| 18 | android:duration="300"/> | ||
| 19 | |||
| 20 | </set> \ No newline at end of file | ||
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml deleted file mode 100644 index c00478946..000000000 --- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml +++ /dev/null | |||
| @@ -1,21 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <!-- This animation is used ONLY when a submenu is replaced. --> | ||
| 5 | <objectAnimator | ||
| 6 | android:propertyName="translationX" | ||
| 7 | android:valueType="floatType" | ||
| 8 | android:valueFrom="0" | ||
| 9 | android:valueTo="-1280dp" | ||
| 10 | android:interpolator="@android:interpolator/decelerate_quad" | ||
| 11 | android:duration="200"/> | ||
| 12 | |||
| 13 | <objectAnimator | ||
| 14 | android:propertyName="alpha" | ||
| 15 | android:valueType="floatType" | ||
| 16 | android:valueFrom="1" | ||
| 17 | android:valueTo="0" | ||
| 18 | android:interpolator="@android:interpolator/decelerate_quad" | ||
| 19 | android:duration="200"/> | ||
| 20 | |||
| 21 | </set> \ No newline at end of file | ||
diff --git a/src/android/app/src/main/res/drawable/shortcut.xml b/src/android/app/src/main/res/drawable/shortcut.xml new file mode 100644 index 000000000..c749e5d72 --- /dev/null +++ b/src/android/app/src/main/res/drawable/shortcut.xml | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <item> | ||
| 5 | <color android:color="@android:color/white" /> | ||
| 6 | </item> | ||
| 7 | <item android:id="@+id/shortcut_foreground"> | ||
| 8 | <bitmap android:src="@drawable/default_icon" /> | ||
| 9 | </item> | ||
| 10 | |||
| 11 | </layer-list> | ||
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml index cbe631d88..406df9eab 100644 --- a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml +++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.constraintlayout.widget.ConstraintLayout | 2 | <RelativeLayout |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 4 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 5 | android:id="@+id/setup_root" | 5 | android:id="@+id/setup_root" |
| @@ -8,33 +8,39 @@ | |||
| 8 | 8 | ||
| 9 | <androidx.viewpager2.widget.ViewPager2 | 9 | <androidx.viewpager2.widget.ViewPager2 |
| 10 | android:id="@+id/viewPager2" | 10 | android:id="@+id/viewPager2" |
| 11 | android:layout_width="0dp" | 11 | android:layout_width="match_parent" |
| 12 | android:layout_height="0dp" | 12 | android:layout_height="match_parent" |
| 13 | app:layout_constraintBottom_toBottomOf="parent" | 13 | android:layout_alignParentTop="true" |
| 14 | app:layout_constraintEnd_toEndOf="parent" | 14 | android:layout_alignParentBottom="true" |
| 15 | app:layout_constraintStart_toStartOf="parent" | 15 | android:clipToPadding="false" /> |
| 16 | app:layout_constraintTop_toTopOf="parent" /> | ||
| 17 | 16 | ||
| 18 | <com.google.android.material.button.MaterialButton | 17 | <androidx.constraintlayout.widget.ConstraintLayout |
| 19 | style="@style/Widget.Material3.Button.TextButton" | 18 | android:id="@+id/constraint_buttons" |
| 20 | android:id="@+id/button_next" | 19 | android:layout_width="match_parent" |
| 21 | android:layout_width="wrap_content" | ||
| 22 | android:layout_height="wrap_content" | 20 | android:layout_height="wrap_content" |
| 23 | android:layout_margin="16dp" | 21 | android:layout_alignParentBottom="true" |
| 24 | android:text="@string/next" | 22 | android:layout_margin="8dp"> |
| 25 | android:visibility="invisible" | ||
| 26 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 27 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 28 | 23 | ||
| 29 | <com.google.android.material.button.MaterialButton | 24 | <com.google.android.material.button.MaterialButton |
| 30 | android:id="@+id/button_back" | 25 | android:id="@+id/button_next" |
| 31 | style="@style/Widget.Material3.Button.TextButton" | 26 | style="@style/Widget.Material3.Button.TextButton" |
| 32 | android:layout_width="wrap_content" | 27 | android:layout_width="wrap_content" |
| 33 | android:layout_height="wrap_content" | 28 | android:layout_height="wrap_content" |
| 34 | android:layout_margin="16dp" | 29 | android:text="@string/next" |
| 35 | android:text="@string/back" | 30 | android:visibility="invisible" |
| 36 | android:visibility="invisible" | 31 | app:layout_constraintBottom_toBottomOf="parent" |
| 37 | app:layout_constraintBottom_toBottomOf="parent" | 32 | app:layout_constraintEnd_toEndOf="parent" /> |
| 38 | app:layout_constraintStart_toStartOf="parent" /> | 33 | |
| 34 | <com.google.android.material.button.MaterialButton | ||
| 35 | android:id="@+id/button_back" | ||
| 36 | style="@style/Widget.Material3.Button.TextButton" | ||
| 37 | android:layout_width="wrap_content" | ||
| 38 | android:layout_height="wrap_content" | ||
| 39 | android:text="@string/back" | ||
| 40 | android:visibility="invisible" | ||
| 41 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 42 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 43 | |||
| 44 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
| 39 | 45 | ||
| 40 | </androidx.constraintlayout.widget.ConstraintLayout> | 46 | </RelativeLayout> |
diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml index e1c26b2f8..9e0ab8ecb 100644 --- a/src/android/app/src/main/res/layout-w600dp/page_setup.xml +++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml | |||
| @@ -21,45 +21,76 @@ | |||
| 21 | 21 | ||
| 22 | </LinearLayout> | 22 | </LinearLayout> |
| 23 | 23 | ||
| 24 | <LinearLayout | 24 | <androidx.constraintlayout.widget.ConstraintLayout |
| 25 | android:layout_width="match_parent" | 25 | android:layout_width="match_parent" |
| 26 | android:layout_height="match_parent" | 26 | android:layout_height="match_parent" |
| 27 | android:layout_weight="1" | 27 | android:layout_weight="1"> |
| 28 | android:orientation="vertical" | ||
| 29 | android:gravity="center"> | ||
| 30 | 28 | ||
| 31 | <com.google.android.material.textview.MaterialTextView | 29 | <com.google.android.material.textview.MaterialTextView |
| 32 | style="@style/TextAppearance.Material3.DisplaySmall" | ||
| 33 | android:id="@+id/text_title" | 30 | android:id="@+id/text_title" |
| 34 | android:layout_width="match_parent" | 31 | style="@style/TextAppearance.Material3.DisplaySmall" |
| 35 | android:layout_height="wrap_content" | 32 | android:layout_width="0dp" |
| 36 | android:textAlignment="center" | 33 | android:layout_height="0dp" |
| 34 | android:gravity="center" | ||
| 37 | android:textColor="?attr/colorOnSurface" | 35 | android:textColor="?attr/colorOnSurface" |
| 38 | android:textStyle="bold" | 36 | android:textStyle="bold" |
| 37 | app:layout_constraintBottom_toTopOf="@+id/text_description" | ||
| 38 | app:layout_constraintEnd_toEndOf="parent" | ||
| 39 | app:layout_constraintStart_toStartOf="parent" | ||
| 40 | app:layout_constraintTop_toTopOf="parent" | ||
| 41 | app:layout_constraintVertical_weight="2" | ||
| 39 | tools:text="@string/welcome" /> | 42 | tools:text="@string/welcome" /> |
| 40 | 43 | ||
| 41 | <com.google.android.material.textview.MaterialTextView | 44 | <com.google.android.material.textview.MaterialTextView |
| 42 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 43 | android:id="@+id/text_description" | 45 | android:id="@+id/text_description" |
| 44 | android:layout_width="match_parent" | 46 | style="@style/TextAppearance.Material3.TitleLarge" |
| 45 | android:layout_height="wrap_content" | 47 | android:layout_width="0dp" |
| 46 | android:layout_marginTop="16dp" | 48 | android:layout_height="0dp" |
| 47 | android:paddingHorizontal="32dp" | 49 | android:gravity="center" |
| 48 | android:textAlignment="center" | 50 | android:textSize="20sp" |
| 49 | android:textSize="26sp" | 51 | android:paddingHorizontal="16dp" |
| 50 | app:lineHeight="40sp" | 52 | app:layout_constraintBottom_toTopOf="@+id/button_action" |
| 53 | app:layout_constraintEnd_toEndOf="parent" | ||
| 54 | app:layout_constraintStart_toStartOf="parent" | ||
| 55 | app:layout_constraintTop_toBottomOf="@+id/text_title" | ||
| 56 | app:layout_constraintVertical_weight="2" | ||
| 57 | app:lineHeight="30sp" | ||
| 51 | tools:text="@string/welcome_description" /> | 58 | tools:text="@string/welcome_description" /> |
| 52 | 59 | ||
| 60 | <com.google.android.material.textview.MaterialTextView | ||
| 61 | android:id="@+id/text_confirmation" | ||
| 62 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 63 | android:layout_width="0dp" | ||
| 64 | android:layout_height="0dp" | ||
| 65 | android:paddingHorizontal="16dp" | ||
| 66 | android:paddingBottom="20dp" | ||
| 67 | android:gravity="center" | ||
| 68 | android:textSize="30sp" | ||
| 69 | android:visibility="invisible" | ||
| 70 | android:text="@string/step_complete" | ||
| 71 | android:textStyle="bold" | ||
| 72 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 73 | app:layout_constraintEnd_toEndOf="parent" | ||
| 74 | app:layout_constraintStart_toStartOf="parent" | ||
| 75 | app:layout_constraintTop_toBottomOf="@+id/text_description" | ||
| 76 | app:layout_constraintVertical_weight="1" | ||
| 77 | app:lineHeight="30sp" /> | ||
| 78 | |||
| 53 | <com.google.android.material.button.MaterialButton | 79 | <com.google.android.material.button.MaterialButton |
| 54 | android:id="@+id/button_action" | 80 | android:id="@+id/button_action" |
| 55 | android:layout_width="wrap_content" | 81 | android:layout_width="wrap_content" |
| 56 | android:layout_height="56dp" | 82 | android:layout_height="56dp" |
| 57 | android:layout_marginTop="32dp" | 83 | android:layout_marginTop="16dp" |
| 84 | android:layout_marginBottom="48dp" | ||
| 58 | android:textSize="20sp" | 85 | android:textSize="20sp" |
| 59 | app:iconSize="24sp" | ||
| 60 | app:iconGravity="end" | 86 | app:iconGravity="end" |
| 87 | app:iconSize="24sp" | ||
| 88 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 89 | app:layout_constraintEnd_toEndOf="parent" | ||
| 90 | app:layout_constraintStart_toStartOf="parent" | ||
| 91 | app:layout_constraintTop_toBottomOf="@+id/text_description" | ||
| 61 | tools:text="Get started" /> | 92 | tools:text="Get started" /> |
| 62 | 93 | ||
| 63 | </LinearLayout> | 94 | </androidx.constraintlayout.widget.ConstraintLayout> |
| 64 | 95 | ||
| 65 | </LinearLayout> | 96 | </LinearLayout> |
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml index 14ae83b04..8a026a30a 100644 --- a/src/android/app/src/main/res/layout/activity_settings.xml +++ b/src/android/app/src/main/res/layout/activity_settings.xml | |||
| @@ -1,42 +1,24 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.coordinatorlayout.widget.CoordinatorLayout | 2 | <androidx.constraintlayout.widget.ConstraintLayout |
| 3 | android:id="@+id/coordinator_main" | ||
| 4 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 5 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 5 | xmlns:tools="http://schemas.android.com/tools" | ||
| 6 | android:id="@+id/constraint_settings" | ||
| 6 | android:layout_width="match_parent" | 7 | android:layout_width="match_parent" |
| 7 | android:layout_height="match_parent" | 8 | android:layout_height="match_parent" |
| 8 | android:background="?attr/colorSurface"> | 9 | android:background="?attr/colorSurface"> |
| 9 | 10 | ||
| 10 | <com.google.android.material.appbar.AppBarLayout | 11 | <androidx.fragment.app.FragmentContainerView |
| 11 | android:id="@+id/appbar_settings" | 12 | android:id="@+id/fragment_container" |
| 12 | android:layout_width="match_parent" | 13 | android:name="androidx.navigation.fragment.NavHostFragment" |
| 13 | android:layout_height="wrap_content" | 14 | android:layout_width="0dp" |
| 14 | android:fitsSystemWindows="true" | 15 | android:layout_height="0dp" |
| 15 | app:elevation="0dp"> | 16 | app:defaultNavHost="true" |
| 16 | 17 | app:layout_constraintBottom_toBottomOf="parent" | |
| 17 | <com.google.android.material.appbar.CollapsingToolbarLayout | 18 | app:layout_constraintLeft_toLeftOf="parent" |
| 18 | style="?attr/collapsingToolbarLayoutMediumStyle" | 19 | app:layout_constraintRight_toRightOf="parent" |
| 19 | android:id="@+id/toolbar_settings_layout" | 20 | app:layout_constraintTop_toTopOf="parent" |
| 20 | android:layout_width="match_parent" | 21 | tools:layout="@layout/fragment_settings" /> |
| 21 | android:layout_height="?attr/collapsingToolbarLayoutMediumSize" | ||
| 22 | app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> | ||
| 23 | |||
| 24 | <com.google.android.material.appbar.MaterialToolbar | ||
| 25 | android:id="@+id/toolbar_settings" | ||
| 26 | android:layout_width="match_parent" | ||
| 27 | android:layout_height="?attr/actionBarSize" | ||
| 28 | app:layout_collapseMode="pin" /> | ||
| 29 | |||
| 30 | </com.google.android.material.appbar.CollapsingToolbarLayout> | ||
| 31 | |||
| 32 | </com.google.android.material.appbar.AppBarLayout> | ||
| 33 | |||
| 34 | <FrameLayout | ||
| 35 | android:id="@+id/frame_content" | ||
| 36 | android:layout_width="match_parent" | ||
| 37 | android:layout_height="match_parent" | ||
| 38 | android:layout_marginHorizontal="12dp" | ||
| 39 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> | ||
| 40 | 22 | ||
| 41 | <View | 23 | <View |
| 42 | android:id="@+id/navigation_bar_shade" | 24 | android:id="@+id/navigation_bar_shade" |
| @@ -45,6 +27,8 @@ | |||
| 45 | android:background="@android:color/transparent" | 27 | android:background="@android:color/transparent" |
| 46 | android:clickable="false" | 28 | android:clickable="false" |
| 47 | android:focusable="false" | 29 | android:focusable="false" |
| 48 | android:layout_gravity="bottom|center_horizontal" /> | 30 | app:layout_constraintBottom_toBottomOf="parent" |
| 31 | app:layout_constraintEnd_toEndOf="parent" | ||
| 32 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 49 | 33 | ||
| 50 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | 34 | </androidx.constraintlayout.widget.ConstraintLayout> |
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml index dc289db17..f9f1d89fb 100644 --- a/src/android/app/src/main/res/layout/card_home_option.xml +++ b/src/android/app/src/main/res/layout/card_home_option.xml | |||
| @@ -53,6 +53,23 @@ | |||
| 53 | android:layout_marginTop="5dp" | 53 | android:layout_marginTop="5dp" |
| 54 | tools:text="@string/install_prod_keys_description" /> | 54 | tools:text="@string/install_prod_keys_description" /> |
| 55 | 55 | ||
| 56 | <com.google.android.material.textview.MaterialTextView | ||
| 57 | style="@style/TextAppearance.Material3.LabelMedium" | ||
| 58 | android:id="@+id/option_detail" | ||
| 59 | android:layout_width="match_parent" | ||
| 60 | android:layout_height="wrap_content" | ||
| 61 | android:textAlignment="viewStart" | ||
| 62 | android:textSize="14sp" | ||
| 63 | android:textStyle="bold" | ||
| 64 | android:singleLine="true" | ||
| 65 | android:marqueeRepeatLimit="marquee_forever" | ||
| 66 | android:ellipsize="none" | ||
| 67 | android:requiresFadingEdge="horizontal" | ||
| 68 | android:layout_marginTop="5dp" | ||
| 69 | android:visibility="gone" | ||
| 70 | tools:visibility="visible" | ||
| 71 | tools:text="/tree/primary:Games" /> | ||
| 72 | |||
| 56 | </LinearLayout> | 73 | </LinearLayout> |
| 57 | 74 | ||
| 58 | </LinearLayout> | 75 | </LinearLayout> |
diff --git a/src/android/app/src/main/res/layout/dialog_slider.xml b/src/android/app/src/main/res/layout/dialog_slider.xml index 8c84cb606..d1cb31739 100644 --- a/src/android/app/src/main/res/layout/dialog_slider.xml +++ b/src/android/app/src/main/res/layout/dialog_slider.xml | |||
| @@ -5,23 +5,16 @@ | |||
| 5 | android:layout_height="wrap_content" | 5 | android:layout_height="wrap_content" |
| 6 | android:orientation="vertical"> | 6 | android:orientation="vertical"> |
| 7 | 7 | ||
| 8 | <TextView | 8 | <com.google.android.material.textview.MaterialTextView |
| 9 | android:id="@+id/text_value" | 9 | android:id="@+id/text_value" |
| 10 | style="@style/TextAppearance.Material3.LabelMedium" | ||
| 10 | android:layout_width="wrap_content" | 11 | android:layout_width="wrap_content" |
| 11 | android:layout_height="wrap_content" | 12 | android:layout_height="wrap_content" |
| 12 | android:layout_alignParentTop="true" | 13 | android:layout_alignParentTop="true" |
| 13 | android:layout_centerHorizontal="true" | 14 | android:layout_centerHorizontal="true" |
| 14 | android:layout_marginBottom="@dimen/spacing_medlarge" | 15 | android:layout_marginBottom="@dimen/spacing_medlarge" |
| 15 | android:layout_marginTop="@dimen/spacing_medlarge" | 16 | android:layout_marginTop="@dimen/spacing_medlarge" |
| 16 | tools:text="75" /> | 17 | tools:text="75%" /> |
| 17 | |||
| 18 | <TextView | ||
| 19 | android:id="@+id/text_units" | ||
| 20 | android:layout_width="wrap_content" | ||
| 21 | android:layout_height="wrap_content" | ||
| 22 | android:layout_alignTop="@+id/text_value" | ||
| 23 | android:layout_toEndOf="@+id/text_value" | ||
| 24 | tools:text="%" /> | ||
| 25 | 18 | ||
| 26 | <com.google.android.material.slider.Slider | 19 | <com.google.android.material.slider.Slider |
| 27 | android:id="@+id/slider" | 20 | android:id="@+id/slider" |
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index e54a10e8f..da97d85c1 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml | |||
| @@ -26,6 +26,81 @@ | |||
| 26 | android:focusable="false" | 26 | android:focusable="false" |
| 27 | android:focusableInTouchMode="false" /> | 27 | android:focusableInTouchMode="false" /> |
| 28 | 28 | ||
| 29 | <com.google.android.material.card.MaterialCardView | ||
| 30 | android:id="@+id/loading_indicator" | ||
| 31 | style="?attr/materialCardViewOutlinedStyle" | ||
| 32 | android:layout_width="wrap_content" | ||
| 33 | android:layout_height="wrap_content" | ||
| 34 | android:layout_gravity="center" | ||
| 35 | android:focusable="false"> | ||
| 36 | |||
| 37 | <androidx.constraintlayout.widget.ConstraintLayout | ||
| 38 | android:id="@+id/loading_layout" | ||
| 39 | android:layout_width="wrap_content" | ||
| 40 | android:layout_height="wrap_content" | ||
| 41 | android:gravity="center_horizontal"> | ||
| 42 | |||
| 43 | <ImageView | ||
| 44 | android:id="@+id/loading_image" | ||
| 45 | android:layout_width="wrap_content" | ||
| 46 | android:layout_height="0dp" | ||
| 47 | android:adjustViewBounds="true" | ||
| 48 | app:layout_constraintBottom_toBottomOf="@+id/linearLayout" | ||
| 49 | app:layout_constraintStart_toStartOf="parent" | ||
| 50 | app:layout_constraintTop_toTopOf="@+id/linearLayout" | ||
| 51 | tools:src="@drawable/default_icon" /> | ||
| 52 | |||
| 53 | <LinearLayout | ||
| 54 | android:id="@+id/linearLayout" | ||
| 55 | android:layout_width="wrap_content" | ||
| 56 | android:layout_height="wrap_content" | ||
| 57 | android:orientation="vertical" | ||
| 58 | android:paddingHorizontal="24dp" | ||
| 59 | android:paddingVertical="36dp" | ||
| 60 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 61 | app:layout_constraintEnd_toEndOf="parent" | ||
| 62 | app:layout_constraintStart_toEndOf="@id/loading_image" | ||
| 63 | app:layout_constraintTop_toTopOf="parent"> | ||
| 64 | |||
| 65 | <com.google.android.material.textview.MaterialTextView | ||
| 66 | android:id="@+id/loading_title" | ||
| 67 | style="@style/TextAppearance.Material3.TitleMedium" | ||
| 68 | android:layout_width="match_parent" | ||
| 69 | android:layout_height="wrap_content" | ||
| 70 | android:ellipsize="marquee" | ||
| 71 | android:marqueeRepeatLimit="marquee_forever" | ||
| 72 | android:requiresFadingEdge="horizontal" | ||
| 73 | android:singleLine="true" | ||
| 74 | android:textAlignment="viewStart" | ||
| 75 | tools:text="@string/games" /> | ||
| 76 | |||
| 77 | <com.google.android.material.textview.MaterialTextView | ||
| 78 | android:id="@+id/loading_text" | ||
| 79 | style="@style/TextAppearance.Material3.TitleSmall" | ||
| 80 | android:layout_width="match_parent" | ||
| 81 | android:layout_height="wrap_content" | ||
| 82 | android:layout_marginTop="4dp" | ||
| 83 | android:ellipsize="marquee" | ||
| 84 | android:marqueeRepeatLimit="marquee_forever" | ||
| 85 | android:requiresFadingEdge="horizontal" | ||
| 86 | android:singleLine="true" | ||
| 87 | android:text="@string/loading" | ||
| 88 | android:textAlignment="viewStart" /> | ||
| 89 | |||
| 90 | <com.google.android.material.progressindicator.LinearProgressIndicator | ||
| 91 | android:id="@+id/loading_progress_indicator" | ||
| 92 | android:layout_width="192dp" | ||
| 93 | android:layout_height="wrap_content" | ||
| 94 | android:layout_marginTop="12dp" | ||
| 95 | android:indeterminate="true" | ||
| 96 | app:trackCornerRadius="8dp" /> | ||
| 97 | |||
| 98 | </LinearLayout> | ||
| 99 | |||
| 100 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
| 101 | |||
| 102 | </com.google.android.material.card.MaterialCardView> | ||
| 103 | |||
| 29 | </FrameLayout> | 104 | </FrameLayout> |
| 30 | 105 | ||
| 31 | <FrameLayout | 106 | <FrameLayout |
| @@ -41,11 +116,12 @@ | |||
| 41 | android:layout_height="match_parent" | 116 | android:layout_height="match_parent" |
| 42 | android:layout_gravity="center" | 117 | android:layout_gravity="center" |
| 43 | android:focusable="true" | 118 | android:focusable="true" |
| 44 | android:focusableInTouchMode="true" /> | 119 | android:focusableInTouchMode="true" |
| 120 | android:visibility="invisible" /> | ||
| 45 | 121 | ||
| 46 | <Button | 122 | <Button |
| 47 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 48 | android:id="@+id/done_control_config" | 123 | android:id="@+id/done_control_config" |
| 124 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 49 | android:layout_width="wrap_content" | 125 | android:layout_width="wrap_content" |
| 50 | android:layout_height="wrap_content" | 126 | android:layout_height="wrap_content" |
| 51 | android:layout_gravity="center" | 127 | android:layout_gravity="center" |
| @@ -81,6 +157,7 @@ | |||
| 81 | android:layout_height="match_parent" | 157 | android:layout_height="match_parent" |
| 82 | android:layout_gravity="start|bottom" | 158 | android:layout_gravity="start|bottom" |
| 83 | app:headerLayout="@layout/header_in_game" | 159 | app:headerLayout="@layout/header_in_game" |
| 84 | app:menu="@menu/menu_in_game" /> | 160 | app:menu="@menu/menu_in_game" |
| 161 | tools:visibility="gone" /> | ||
| 85 | 162 | ||
| 86 | </androidx.drawerlayout.widget.DrawerLayout> | 163 | </androidx.drawerlayout.widget.DrawerLayout> |
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml index 167720347..ebedbf1ec 100644 --- a/src/android/app/src/main/res/layout/fragment_settings.xml +++ b/src/android/app/src/main/res/layout/fragment_settings.xml | |||
| @@ -1,14 +1,41 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <FrameLayout | 2 | <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | android:id="@+id/coordinator_main" | ||
| 4 | android:layout_width="match_parent" | 5 | android:layout_width="match_parent" |
| 5 | android:layout_height="match_parent"> | 6 | android:layout_height="match_parent" |
| 7 | android:background="?attr/colorSurface"> | ||
| 8 | |||
| 9 | <com.google.android.material.appbar.AppBarLayout | ||
| 10 | android:id="@+id/appbar_settings" | ||
| 11 | android:layout_width="match_parent" | ||
| 12 | android:layout_height="wrap_content" | ||
| 13 | android:fitsSystemWindows="true" | ||
| 14 | app:elevation="0dp"> | ||
| 15 | |||
| 16 | <com.google.android.material.appbar.CollapsingToolbarLayout | ||
| 17 | android:id="@+id/toolbar_settings_layout" | ||
| 18 | style="?attr/collapsingToolbarLayoutMediumStyle" | ||
| 19 | android:layout_width="match_parent" | ||
| 20 | android:layout_height="?attr/collapsingToolbarLayoutMediumSize" | ||
| 21 | app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> | ||
| 22 | |||
| 23 | <com.google.android.material.appbar.MaterialToolbar | ||
| 24 | android:id="@+id/toolbar_settings" | ||
| 25 | android:layout_width="match_parent" | ||
| 26 | android:layout_height="?attr/actionBarSize" | ||
| 27 | app:layout_collapseMode="pin" | ||
| 28 | app:navigationIcon="@drawable/ic_back" /> | ||
| 29 | |||
| 30 | </com.google.android.material.appbar.CollapsingToolbarLayout> | ||
| 31 | |||
| 32 | </com.google.android.material.appbar.AppBarLayout> | ||
| 6 | 33 | ||
| 7 | <androidx.recyclerview.widget.RecyclerView | 34 | <androidx.recyclerview.widget.RecyclerView |
| 8 | android:id="@+id/list_settings" | 35 | android:id="@+id/list_settings" |
| 9 | android:layout_width="match_parent" | 36 | android:layout_width="match_parent" |
| 10 | android:layout_height="match_parent" | 37 | android:layout_height="match_parent" |
| 11 | android:background="?attr/colorSurface" | 38 | android:clipToPadding="false" |
| 12 | android:clipToPadding="false" /> | 39 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> |
| 13 | 40 | ||
| 14 | </FrameLayout> | 41 | </androidx.coordinatorlayout.widget.CoordinatorLayout> |
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml new file mode 100644 index 000000000..c779ed2fc --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | android:layout_width="match_parent" | ||
| 6 | android:layout_height="match_parent"> | ||
| 7 | |||
| 8 | <RelativeLayout | ||
| 9 | android:id="@+id/relativeLayout" | ||
| 10 | android:layout_width="0dp" | ||
| 11 | android:layout_height="0dp" | ||
| 12 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 13 | app:layout_constraintEnd_toEndOf="parent" | ||
| 14 | app:layout_constraintStart_toStartOf="parent" | ||
| 15 | app:layout_constraintTop_toBottomOf="@+id/divider"> | ||
| 16 | |||
| 17 | <LinearLayout | ||
| 18 | android:id="@+id/no_results_view" | ||
| 19 | android:layout_width="match_parent" | ||
| 20 | android:layout_height="match_parent" | ||
| 21 | android:gravity="center" | ||
| 22 | android:orientation="vertical"> | ||
| 23 | |||
| 24 | <ImageView | ||
| 25 | android:id="@+id/icon_no_results" | ||
| 26 | android:layout_width="match_parent" | ||
| 27 | android:layout_height="80dp" | ||
| 28 | android:src="@drawable/ic_search" /> | ||
| 29 | |||
| 30 | <com.google.android.material.textview.MaterialTextView | ||
| 31 | android:id="@+id/notice_text" | ||
| 32 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 33 | android:layout_width="match_parent" | ||
| 34 | android:layout_height="wrap_content" | ||
| 35 | android:gravity="center" | ||
| 36 | android:paddingTop="8dp" | ||
| 37 | android:text="@string/search_settings" | ||
| 38 | tools:visibility="visible" /> | ||
| 39 | |||
| 40 | </LinearLayout> | ||
| 41 | |||
| 42 | <androidx.recyclerview.widget.RecyclerView | ||
| 43 | android:id="@+id/settings_list" | ||
| 44 | android:layout_width="match_parent" | ||
| 45 | android:layout_height="match_parent" | ||
| 46 | android:clipToPadding="false" /> | ||
| 47 | |||
| 48 | </RelativeLayout> | ||
| 49 | |||
| 50 | <FrameLayout | ||
| 51 | android:id="@+id/frame_search" | ||
| 52 | android:layout_width="match_parent" | ||
| 53 | android:layout_height="wrap_content" | ||
| 54 | android:clipToPadding="false" | ||
| 55 | app:layout_constraintEnd_toEndOf="parent" | ||
| 56 | app:layout_constraintStart_toStartOf="parent" | ||
| 57 | app:layout_constraintTop_toTopOf="parent"> | ||
| 58 | |||
| 59 | <com.google.android.material.card.MaterialCardView | ||
| 60 | android:id="@+id/search_background" | ||
| 61 | style="?attr/materialCardViewFilledStyle" | ||
| 62 | android:layout_width="match_parent" | ||
| 63 | android:layout_height="56dp" | ||
| 64 | app:cardCornerRadius="28dp"> | ||
| 65 | |||
| 66 | <LinearLayout | ||
| 67 | android:id="@+id/search_container" | ||
| 68 | android:layout_width="match_parent" | ||
| 69 | android:layout_height="match_parent" | ||
| 70 | android:layout_marginEnd="56dp" | ||
| 71 | android:orientation="horizontal"> | ||
| 72 | |||
| 73 | <Button | ||
| 74 | android:id="@+id/back_button" | ||
| 75 | style="?attr/materialIconButtonFilledTonalStyle" | ||
| 76 | android:layout_width="wrap_content" | ||
| 77 | android:layout_height="wrap_content" | ||
| 78 | android:layout_gravity="center_vertical" | ||
| 79 | android:layout_marginStart="8dp" | ||
| 80 | app:backgroundTint="@android:color/transparent" | ||
| 81 | app:icon="@drawable/ic_back" /> | ||
| 82 | |||
| 83 | <EditText | ||
| 84 | android:id="@+id/search_text" | ||
| 85 | android:layout_width="match_parent" | ||
| 86 | android:layout_height="match_parent" | ||
| 87 | android:background="@android:color/transparent" | ||
| 88 | android:hint="@string/search_settings" | ||
| 89 | android:imeOptions="flagNoFullscreen" | ||
| 90 | android:inputType="text" | ||
| 91 | android:maxLines="1" /> | ||
| 92 | |||
| 93 | </LinearLayout> | ||
| 94 | |||
| 95 | <Button | ||
| 96 | android:id="@+id/clear_button" | ||
| 97 | style="?attr/materialIconButtonFilledTonalStyle" | ||
| 98 | android:layout_width="wrap_content" | ||
| 99 | android:layout_height="wrap_content" | ||
| 100 | android:layout_gravity="center_vertical|end" | ||
| 101 | android:layout_marginEnd="8dp" | ||
| 102 | android:visibility="invisible" | ||
| 103 | app:backgroundTint="@android:color/transparent" | ||
| 104 | app:icon="@drawable/ic_clear" | ||
| 105 | tools:visibility="visible" /> | ||
| 106 | |||
| 107 | </com.google.android.material.card.MaterialCardView> | ||
| 108 | |||
| 109 | </FrameLayout> | ||
| 110 | |||
| 111 | <com.google.android.material.divider.MaterialDivider | ||
| 112 | android:id="@+id/divider" | ||
| 113 | android:layout_width="match_parent" | ||
| 114 | android:layout_height="wrap_content" | ||
| 115 | android:layout_marginTop="20dp" | ||
| 116 | app:layout_constraintEnd_toEndOf="parent" | ||
| 117 | app:layout_constraintStart_toStartOf="parent" | ||
| 118 | app:layout_constraintTop_toBottomOf="@+id/frame_search" /> | ||
| 119 | |||
| 120 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml index d7bafaea2..9499f6463 100644 --- a/src/android/app/src/main/res/layout/fragment_setup.xml +++ b/src/android/app/src/main/res/layout/fragment_setup.xml | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.constraintlayout.widget.ConstraintLayout | 2 | <RelativeLayout |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 4 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 5 | android:id="@+id/setup_root" | 5 | android:id="@+id/setup_root" |
| @@ -8,35 +8,39 @@ | |||
| 8 | 8 | ||
| 9 | <androidx.viewpager2.widget.ViewPager2 | 9 | <androidx.viewpager2.widget.ViewPager2 |
| 10 | android:id="@+id/viewPager2" | 10 | android:id="@+id/viewPager2" |
| 11 | android:layout_width="0dp" | 11 | android:layout_width="match_parent" |
| 12 | android:layout_height="0dp" | ||
| 13 | android:clipToPadding="false" | ||
| 14 | android:layout_marginBottom="16dp" | ||
| 15 | app:layout_constraintBottom_toTopOf="@+id/button_next" | ||
| 16 | app:layout_constraintEnd_toEndOf="parent" | ||
| 17 | app:layout_constraintStart_toStartOf="parent" | ||
| 18 | app:layout_constraintTop_toTopOf="parent" /> | ||
| 19 | |||
| 20 | <com.google.android.material.button.MaterialButton | ||
| 21 | style="@style/Widget.Material3.Button.TextButton" | ||
| 22 | android:id="@+id/button_next" | ||
| 23 | android:layout_width="wrap_content" | ||
| 24 | android:layout_height="wrap_content" | 12 | android:layout_height="wrap_content" |
| 25 | android:layout_margin="12dp" | 13 | android:layout_above="@+id/constraint_buttons" |
| 26 | android:text="@string/next" | 14 | android:layout_alignParentTop="true" |
| 27 | android:visibility="invisible" | 15 | android:clipToPadding="false" /> |
| 28 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 29 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 30 | 16 | ||
| 31 | <com.google.android.material.button.MaterialButton | 17 | <androidx.constraintlayout.widget.ConstraintLayout |
| 32 | style="@style/Widget.Material3.Button.TextButton" | 18 | android:id="@+id/constraint_buttons" |
| 33 | android:id="@+id/button_back" | 19 | android:layout_width="match_parent" |
| 34 | android:layout_width="wrap_content" | ||
| 35 | android:layout_height="wrap_content" | 20 | android:layout_height="wrap_content" |
| 36 | android:layout_margin="12dp" | 21 | android:layout_margin="8dp" |
| 37 | android:text="@string/back" | 22 | android:layout_alignParentBottom="true"> |
| 38 | android:visibility="invisible" | 23 | |
| 39 | app:layout_constraintBottom_toBottomOf="parent" | 24 | <com.google.android.material.button.MaterialButton |
| 40 | app:layout_constraintStart_toStartOf="parent" /> | 25 | android:id="@+id/button_next" |
| 26 | style="@style/Widget.Material3.Button.TextButton" | ||
| 27 | android:layout_width="wrap_content" | ||
| 28 | android:layout_height="wrap_content" | ||
| 29 | android:text="@string/next" | ||
| 30 | android:visibility="invisible" | ||
| 31 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 32 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 33 | |||
| 34 | <com.google.android.material.button.MaterialButton | ||
| 35 | android:id="@+id/button_back" | ||
| 36 | style="@style/Widget.Material3.Button.TextButton" | ||
| 37 | android:layout_width="wrap_content" | ||
| 38 | android:layout_height="wrap_content" | ||
| 39 | android:text="@string/back" | ||
| 40 | android:visibility="invisible" | ||
| 41 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 42 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 43 | |||
| 44 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
| 41 | 45 | ||
| 42 | </androidx.constraintlayout.widget.ConstraintLayout> | 46 | </RelativeLayout> |
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml index ec896342b..f1037a740 100644 --- a/src/android/app/src/main/res/layout/list_item_setting.xml +++ b/src/android/app/src/main/res/layout/list_item_setting.xml | |||
| @@ -1,9 +1,10 @@ | |||
| 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 |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 4 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 3 | xmlns:tools="http://schemas.android.com/tools" | 5 | xmlns:tools="http://schemas.android.com/tools" |
| 4 | android:layout_width="match_parent" | 6 | android:layout_width="match_parent" |
| 5 | android:layout_height="wrap_content" | 7 | android:layout_height="wrap_content" |
| 6 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 7 | android:background="?android:attr/selectableItemBackground" | 8 | android:background="?android:attr/selectableItemBackground" |
| 8 | android:clickable="true" | 9 | android:clickable="true" |
| 9 | android:focusable="true" | 10 | android:focusable="true" |
| @@ -11,31 +12,40 @@ | |||
| 11 | android:minHeight="72dp" | 12 | android:minHeight="72dp" |
| 12 | android:padding="@dimen/spacing_large"> | 13 | android:padding="@dimen/spacing_large"> |
| 13 | 14 | ||
| 14 | <com.google.android.material.textview.MaterialTextView | 15 | <LinearLayout |
| 15 | style="@style/TextAppearance.Material3.HeadlineMedium" | 16 | android:layout_width="match_parent" |
| 16 | android:id="@+id/text_setting_name" | ||
| 17 | android:layout_width="0dp" | ||
| 18 | android:layout_height="wrap_content" | 17 | android:layout_height="wrap_content" |
| 19 | android:layout_alignParentEnd="true" | 18 | android:orientation="vertical"> |
| 20 | android:layout_alignParentStart="true" | ||
| 21 | android:layout_alignParentTop="true" | ||
| 22 | android:textSize="16sp" | ||
| 23 | android:textAlignment="viewStart" | ||
| 24 | app:lineHeight="28dp" | ||
| 25 | tools:text="Setting Name" /> | ||
| 26 | 19 | ||
| 27 | <TextView | 20 | <com.google.android.material.textview.MaterialTextView |
| 28 | style="@style/TextAppearance.Material3.BodySmall" | 21 | android:id="@+id/text_setting_name" |
| 29 | android:id="@+id/text_setting_description" | 22 | style="@style/TextAppearance.Material3.HeadlineMedium" |
| 30 | android:layout_width="wrap_content" | 23 | android:layout_width="match_parent" |
| 31 | android:layout_height="wrap_content" | 24 | android:layout_height="wrap_content" |
| 32 | android:layout_alignParentEnd="true" | 25 | android:textAlignment="viewStart" |
| 33 | android:layout_alignParentStart="true" | 26 | android:textSize="16sp" |
| 34 | android:layout_alignStart="@+id/text_setting_name" | 27 | app:lineHeight="22dp" |
| 35 | android:layout_below="@+id/text_setting_name" | 28 | tools:text="Setting Name" /> |
| 36 | android:layout_marginTop="@dimen/spacing_small" | 29 | |
| 37 | android:visibility="visible" | 30 | <com.google.android.material.textview.MaterialTextView |
| 38 | android:textAlignment="viewStart" | 31 | android:id="@+id/text_setting_description" |
| 39 | tools:text="@string/app_disclaimer" /> | 32 | style="@style/TextAppearance.Material3.BodySmall" |
| 33 | android:layout_width="match_parent" | ||
| 34 | android:layout_height="wrap_content" | ||
| 35 | android:layout_marginTop="@dimen/spacing_small" | ||
| 36 | android:textAlignment="viewStart" | ||
| 37 | tools:text="@string/app_disclaimer" /> | ||
| 38 | |||
| 39 | <com.google.android.material.textview.MaterialTextView | ||
| 40 | android:id="@+id/text_setting_value" | ||
| 41 | style="@style/TextAppearance.Material3.LabelMedium" | ||
| 42 | android:layout_width="match_parent" | ||
| 43 | android:layout_height="wrap_content" | ||
| 44 | android:layout_marginTop="@dimen/spacing_small" | ||
| 45 | android:textAlignment="viewStart" | ||
| 46 | android:textStyle="bold" | ||
| 47 | tools:text="1x" /> | ||
| 48 | |||
| 49 | </LinearLayout> | ||
| 40 | 50 | ||
| 41 | </RelativeLayout> | 51 | </RelativeLayout> |
diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml index 1436ef308..535abcf02 100644 --- a/src/android/app/src/main/res/layout/page_setup.xml +++ b/src/android/app/src/main/res/layout/page_setup.xml | |||
| @@ -21,11 +21,12 @@ | |||
| 21 | app:layout_constraintVertical_chainStyle="spread" | 21 | app:layout_constraintVertical_chainStyle="spread" |
| 22 | app:layout_constraintWidth_max="220dp" | 22 | app:layout_constraintWidth_max="220dp" |
| 23 | app:layout_constraintWidth_min="110dp" | 23 | app:layout_constraintWidth_min="110dp" |
| 24 | app:layout_constraintVertical_weight="3" /> | 24 | app:layout_constraintVertical_weight="3" |
| 25 | tools:src="@drawable/ic_notification" /> | ||
| 25 | 26 | ||
| 26 | <com.google.android.material.textview.MaterialTextView | 27 | <com.google.android.material.textview.MaterialTextView |
| 27 | android:id="@+id/text_title" | 28 | android:id="@+id/text_title" |
| 28 | style="@style/TextAppearance.Material3.DisplayMedium" | 29 | style="@style/TextAppearance.Material3.DisplaySmall" |
| 29 | android:layout_width="0dp" | 30 | android:layout_width="0dp" |
| 30 | android:layout_height="0dp" | 31 | android:layout_height="0dp" |
| 31 | android:textAlignment="center" | 32 | android:textAlignment="center" |
| @@ -44,23 +45,42 @@ | |||
| 44 | android:layout_width="0dp" | 45 | android:layout_width="0dp" |
| 45 | android:layout_height="0dp" | 46 | android:layout_height="0dp" |
| 46 | android:textAlignment="center" | 47 | android:textAlignment="center" |
| 47 | android:textSize="26sp" | 48 | android:textSize="20sp" |
| 48 | android:paddingHorizontal="16dp" | 49 | android:paddingHorizontal="16dp" |
| 49 | app:layout_constraintBottom_toTopOf="@+id/button_action" | 50 | app:layout_constraintBottom_toTopOf="@+id/button_action" |
| 50 | app:layout_constraintEnd_toEndOf="parent" | 51 | app:layout_constraintEnd_toEndOf="parent" |
| 51 | app:layout_constraintStart_toStartOf="parent" | 52 | app:layout_constraintStart_toStartOf="parent" |
| 52 | app:layout_constraintTop_toBottomOf="@+id/text_title" | 53 | app:layout_constraintTop_toBottomOf="@+id/text_title" |
| 53 | app:layout_constraintVertical_weight="2" | 54 | app:layout_constraintVertical_weight="2" |
| 54 | app:lineHeight="40sp" | 55 | app:lineHeight="30sp" |
| 55 | tools:text="@string/welcome_description" /> | 56 | tools:text="@string/welcome_description" /> |
| 56 | 57 | ||
| 58 | <com.google.android.material.textview.MaterialTextView | ||
| 59 | android:id="@+id/text_confirmation" | ||
| 60 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 61 | android:layout_width="wrap_content" | ||
| 62 | android:layout_height="0dp" | ||
| 63 | android:paddingHorizontal="16dp" | ||
| 64 | android:paddingTop="24dp" | ||
| 65 | android:textAlignment="center" | ||
| 66 | android:textSize="30sp" | ||
| 67 | android:visibility="invisible" | ||
| 68 | android:text="@string/step_complete" | ||
| 69 | android:textStyle="bold" | ||
| 70 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 71 | app:layout_constraintEnd_toEndOf="parent" | ||
| 72 | app:layout_constraintStart_toStartOf="parent" | ||
| 73 | app:layout_constraintTop_toBottomOf="@+id/text_description" | ||
| 74 | app:layout_constraintVertical_weight="1" | ||
| 75 | app:lineHeight="30sp" /> | ||
| 76 | |||
| 57 | <com.google.android.material.button.MaterialButton | 77 | <com.google.android.material.button.MaterialButton |
| 58 | android:id="@+id/button_action" | 78 | android:id="@+id/button_action" |
| 59 | android:layout_width="wrap_content" | 79 | android:layout_width="wrap_content" |
| 60 | android:layout_height="56dp" | 80 | android:layout_height="56dp" |
| 61 | android:textSize="20sp" | ||
| 62 | android:layout_marginTop="16dp" | 81 | android:layout_marginTop="16dp" |
| 63 | android:layout_marginBottom="48dp" | 82 | android:layout_marginBottom="48dp" |
| 83 | android:textSize="20sp" | ||
| 64 | app:iconGravity="end" | 84 | app:iconGravity="end" |
| 65 | app:iconSize="24sp" | 85 | app:iconSize="24sp" |
| 66 | app:layout_constraintBottom_toBottomOf="parent" | 86 | app:layout_constraintBottom_toBottomOf="parent" |
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml index 1fe7aa6d4..21501a471 100644 --- a/src/android/app/src/main/res/menu/menu_settings.xml +++ b/src/android/app/src/main/res/menu/menu_settings.xml | |||
| @@ -1,2 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <menu /> \ No newline at end of file | 2 | <menu xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto"> | ||
| 4 | |||
| 5 | <item | ||
| 6 | android:id="@+id/action_search" | ||
| 7 | android:icon="@drawable/ic_search" | ||
| 8 | android:title="@string/home_search" | ||
| 9 | app:showAsAction="always" /> | ||
| 10 | |||
| 11 | </menu> | ||
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml index 8208f4c2c..cfc494b3f 100644 --- a/src/android/app/src/main/res/navigation/emulation_navigation.xml +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml | |||
| @@ -12,7 +12,26 @@ | |||
| 12 | tools:layout="@layout/fragment_emulation" > | 12 | tools:layout="@layout/fragment_emulation" > |
| 13 | <argument | 13 | <argument |
| 14 | android:name="game" | 14 | android:name="game" |
| 15 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | 15 | app:argType="org.yuzu.yuzu_emu.model.Game" |
| 16 | app:nullable="true" | ||
| 17 | android:defaultValue="@null" /> | ||
| 16 | </fragment> | 18 | </fragment> |
| 17 | 19 | ||
| 20 | <activity | ||
| 21 | android:id="@+id/settingsActivity" | ||
| 22 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity" | ||
| 23 | android:label="SettingsActivity"> | ||
| 24 | <argument | ||
| 25 | android:name="game" | ||
| 26 | app:argType="org.yuzu.yuzu_emu.model.Game" | ||
| 27 | app:nullable="true" /> | ||
| 28 | <argument | ||
| 29 | android:name="menuTag" | ||
| 30 | app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" /> | ||
| 31 | </activity> | ||
| 32 | |||
| 33 | <action | ||
| 34 | android:id="@+id/action_global_settingsActivity" | ||
| 35 | app:destination="@id/settingsActivity" /> | ||
| 36 | |||
| 18 | </navigation> | 37 | </navigation> |
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index fcebba726..2e0ce7a3d 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -62,7 +62,9 @@ | |||
| 62 | android:label="EmulationActivity"> | 62 | android:label="EmulationActivity"> |
| 63 | <argument | 63 | <argument |
| 64 | android:name="game" | 64 | android:name="game" |
| 65 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | 65 | app:argType="org.yuzu.yuzu_emu.model.Game" |
| 66 | app:nullable="true" | ||
| 67 | android:defaultValue="@null" /> | ||
| 66 | </activity> | 68 | </activity> |
| 67 | 69 | ||
| 68 | <action | 70 | <action |
| @@ -70,4 +72,21 @@ | |||
| 70 | app:destination="@id/emulationActivity" | 72 | app:destination="@id/emulationActivity" |
| 71 | app:launchSingleTop="true" /> | 73 | app:launchSingleTop="true" /> |
| 72 | 74 | ||
| 75 | <activity | ||
| 76 | android:id="@+id/settingsActivity" | ||
| 77 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity" | ||
| 78 | android:label="SettingsActivity"> | ||
| 79 | <argument | ||
| 80 | android:name="game" | ||
| 81 | app:argType="org.yuzu.yuzu_emu.model.Game" | ||
| 82 | app:nullable="true" /> | ||
| 83 | <argument | ||
| 84 | android:name="menuTag" | ||
| 85 | app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" /> | ||
| 86 | </activity> | ||
| 87 | |||
| 88 | <action | ||
| 89 | android:id="@+id/action_global_settingsActivity" | ||
| 90 | app:destination="@id/settingsActivity" /> | ||
| 91 | |||
| 73 | </navigation> | 92 | </navigation> |
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml new file mode 100644 index 000000000..1d87d36b3 --- /dev/null +++ b/src/android/app/src/main/res/navigation/settings_navigation.xml | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <navigation xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | android:id="@+id/settings_navigation" | ||
| 5 | app:startDestination="@id/settingsFragment"> | ||
| 6 | |||
| 7 | <fragment | ||
| 8 | android:id="@+id/settingsFragment" | ||
| 9 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment" | ||
| 10 | android:label="SettingsFragment"> | ||
| 11 | <argument | ||
| 12 | android:name="menuTag" | ||
| 13 | app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" /> | ||
| 14 | <argument | ||
| 15 | android:name="game" | ||
| 16 | app:argType="org.yuzu.yuzu_emu.model.Game" | ||
| 17 | app:nullable="true" /> | ||
| 18 | <action | ||
| 19 | android:id="@+id/action_settingsFragment_to_settingsSearchFragment" | ||
| 20 | app:destination="@id/settingsSearchFragment" /> | ||
| 21 | </fragment> | ||
| 22 | |||
| 23 | <action | ||
| 24 | android:id="@+id/action_global_settingsFragment" | ||
| 25 | app:destination="@id/settingsFragment" /> | ||
| 26 | |||
| 27 | <fragment | ||
| 28 | android:id="@+id/settingsSearchFragment" | ||
| 29 | android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment" | ||
| 30 | android:label="SettingsSearchFragment" /> | ||
| 31 | |||
| 32 | </navigation> | ||
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 0c1d91264..daaa7ffde 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml | |||
| @@ -209,7 +209,6 @@ | |||
| 209 | <string name="emulation_pause">Emulation pausieren</string> | 209 | <string name="emulation_pause">Emulation pausieren</string> |
| 210 | <string name="emulation_unpause">Emulation fortsetzen</string> | 210 | <string name="emulation_unpause">Emulation fortsetzen</string> |
| 211 | <string name="emulation_input_overlay">Overlay-Optionen</string> | 211 | <string name="emulation_input_overlay">Overlay-Optionen</string> |
| 212 | <string name="emulation_game_loading">Spiel lädt…</string> | ||
| 213 | 212 | ||
| 214 | <string name="load_settings">Lädt Einstellungen...</string> | 213 | <string name="load_settings">Lädt Einstellungen...</string> |
| 215 | 214 | ||
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 357f956d1..e9129cb00 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pausar Emulación</string> | 213 | <string name="emulation_pause">Pausar Emulación</string> |
| 214 | <string name="emulation_unpause">Reanudar Emulación</string> | 214 | <string name="emulation_unpause">Reanudar Emulación</string> |
| 215 | <string name="emulation_input_overlay">Opciones de pantalla </string> | 215 | <string name="emulation_input_overlay">Opciones de pantalla </string> |
| 216 | <string name="emulation_game_loading">Cargando juego...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Cargando configuración...</string> | 217 | <string name="load_settings">Cargando configuración...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index dfca1c830..2d99d618e 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Mettre en pause l\'émulation</string> | 213 | <string name="emulation_pause">Mettre en pause l\'émulation</string> |
| 214 | <string name="emulation_unpause">Reprendre l\'émulation</string> | 214 | <string name="emulation_unpause">Reprendre l\'émulation</string> |
| 215 | <string name="emulation_input_overlay">Options de l\'overlay</string> | 215 | <string name="emulation_input_overlay">Options de l\'overlay</string> |
| 216 | <string name="emulation_game_loading">Chargement du jeu...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Chargement des paramètres…</string> | 217 | <string name="load_settings">Chargement des paramètres…</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index 089d93ed6..d9c3de385 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Metti in pausa l\'emulazione</string> | 213 | <string name="emulation_pause">Metti in pausa l\'emulazione</string> |
| 214 | <string name="emulation_unpause">Riprendi Emulazione</string> | 214 | <string name="emulation_unpause">Riprendi Emulazione</string> |
| 215 | <string name="emulation_input_overlay">Impostazioni Overlay</string> | 215 | <string name="emulation_input_overlay">Impostazioni Overlay</string> |
| 216 | <string name="emulation_game_loading">Caricamento del gioco...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Caricamento delle impostazioni...</string> | 217 | <string name="load_settings">Caricamento delle impostazioni...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 39b590bee..7a226cd5c 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml | |||
| @@ -211,7 +211,6 @@ | |||
| 211 | <string name="emulation_pause">エミュレーションを一時停止</string> | 211 | <string name="emulation_pause">エミュレーションを一時停止</string> |
| 212 | <string name="emulation_unpause">エミュレーションを再開</string> | 212 | <string name="emulation_unpause">エミュレーションを再開</string> |
| 213 | <string name="emulation_input_overlay">オーバーレイオプション</string> | 213 | <string name="emulation_input_overlay">オーバーレイオプション</string> |
| 214 | <string name="emulation_game_loading">ロード中…</string> | ||
| 215 | 214 | ||
| 216 | <string name="load_settings">設定をロード中…</string> | 215 | <string name="load_settings">設定をロード中…</string> |
| 217 | 216 | ||
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index cbcb2873f..427b6e5a0 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">에뮬레이션 일시 중지</string> | 213 | <string name="emulation_pause">에뮬레이션 일시 중지</string> |
| 214 | <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string> | 214 | <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string> |
| 215 | <string name="emulation_input_overlay">오버레이 옵션</string> | 215 | <string name="emulation_input_overlay">오버레이 옵션</string> |
| 216 | <string name="emulation_game_loading">게임 불러오기 중...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">설정 불러오기 중...</string> | 217 | <string name="load_settings">설정 불러오기 중...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index e48a4be38..ce8d7a9e4 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pause Emulering</string> | 213 | <string name="emulation_pause">Pause Emulering</string> |
| 214 | <string name="emulation_unpause">Opphev pausing av emulering</string> | 214 | <string name="emulation_unpause">Opphev pausing av emulering</string> |
| 215 | <string name="emulation_input_overlay">Alternativer for overlegg</string> | 215 | <string name="emulation_input_overlay">Alternativer for overlegg</string> |
| 216 | <string name="emulation_game_loading">Spillet lastes inn...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Laster inn innstillinger...</string> | 217 | <string name="load_settings">Laster inn innstillinger...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index bc9c0f7f4..c2c24b48f 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Wstrzymaj emulację</string> | 213 | <string name="emulation_pause">Wstrzymaj emulację</string> |
| 214 | <string name="emulation_unpause">Wznów emulację</string> | 214 | <string name="emulation_unpause">Wznów emulację</string> |
| 215 | <string name="emulation_input_overlay">Opcje nakładki</string> | 215 | <string name="emulation_input_overlay">Opcje nakładki</string> |
| 216 | <string name="emulation_game_loading">Wczytywanie gry...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Wczytywanie ustawień...</string> | 217 | <string name="load_settings">Wczytywanie ustawień...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 75fe0edbf..04f276108 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pausa emulação</string> | 213 | <string name="emulation_pause">Pausa emulação</string> |
| 214 | <string name="emulation_unpause">Retomar emulação</string> | 214 | <string name="emulation_unpause">Retomar emulação</string> |
| 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> | 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> |
| 216 | <string name="emulation_game_loading">Jogo a carregar...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Configurações a carregar...</string> | 217 | <string name="load_settings">Configurações a carregar...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 96b040c66..66a3a1a2e 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pausa emulação</string> | 213 | <string name="emulation_pause">Pausa emulação</string> |
| 214 | <string name="emulation_unpause">Retomar emulação</string> | 214 | <string name="emulation_unpause">Retomar emulação</string> |
| 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> | 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> |
| 216 | <string name="emulation_game_loading">Jogo a carregar...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Configurações a carregar...</string> | 217 | <string name="load_settings">Configurações a carregar...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 8d954f59e..f770e954f 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Пауза эмуляции</string> | 213 | <string name="emulation_pause">Пауза эмуляции</string> |
| 214 | <string name="emulation_unpause">Возобновление эмуляции</string> | 214 | <string name="emulation_unpause">Возобновление эмуляции</string> |
| 215 | <string name="emulation_input_overlay">Настройки оверлея</string> | 215 | <string name="emulation_input_overlay">Настройки оверлея</string> |
| 216 | <string name="emulation_game_loading">Загрузка игры...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Загрузка настроек...</string> | 217 | <string name="load_settings">Загрузка настроек...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 6c028535b..ea3ab1b15 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Пауза емуляції</string> | 213 | <string name="emulation_pause">Пауза емуляції</string> |
| 214 | <string name="emulation_unpause">Відновлення емуляції</string> | 214 | <string name="emulation_unpause">Відновлення емуляції</string> |
| 215 | <string name="emulation_input_overlay">Налаштування оверлея</string> | 215 | <string name="emulation_input_overlay">Налаштування оверлея</string> |
| 216 | <string name="emulation_game_loading">Завантаження гри...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Завантаження налаштувань...</string> | 217 | <string name="load_settings">Завантаження налаштувань...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index e4ad2ed07..b45a5a528 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">暂停模拟</string> | 213 | <string name="emulation_pause">暂停模拟</string> |
| 214 | <string name="emulation_unpause">继续模拟</string> | 214 | <string name="emulation_unpause">继续模拟</string> |
| 215 | <string name="emulation_input_overlay">虚拟按键选项</string> | 215 | <string name="emulation_input_overlay">虚拟按键选项</string> |
| 216 | <string name="emulation_game_loading">载入游戏中…</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">正在载入设定…</string> | 217 | <string name="load_settings">正在载入设定…</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 0d32f23df..3aab889e4 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">暫停模擬</string> | 213 | <string name="emulation_pause">暫停模擬</string> |
| 214 | <string name="emulation_unpause">取消暫停模擬</string> | 214 | <string name="emulation_unpause">取消暫停模擬</string> |
| 215 | <string name="emulation_input_overlay">覆疊選項</string> | 215 | <string name="emulation_input_overlay">覆疊選項</string> |
| 216 | <string name="emulation_game_loading">遊戲正在載入…</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">正在載入設定…</string> | 217 | <string name="load_settings">正在載入設定…</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 200b99185..dc10159c9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml | |||
| @@ -243,10 +243,10 @@ | |||
| 243 | <item>@string/cubeb</item> | 243 | <item>@string/cubeb</item> |
| 244 | <item>@string/string_null</item> | 244 | <item>@string/string_null</item> |
| 245 | </string-array> | 245 | </string-array> |
| 246 | <string-array name="outputEngineValues"> | 246 | <integer-array name="outputEngineValues"> |
| 247 | <item>auto</item> | 247 | <item>0</item> |
| 248 | <item>cubeb</item> | 248 | <item>1</item> |
| 249 | <item>null</item> | 249 | <item>3</item> |
| 250 | </string-array> | 250 | </integer-array> |
| 251 | 251 | ||
| 252 | </resources> | 252 | </resources> |
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index 00757e5e8..7b2296d95 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | <dimen name="spacing_refresh_end">72dp</dimen> | 12 | <dimen name="spacing_refresh_end">72dp</dimen> |
| 13 | <dimen name="menu_width">256dp</dimen> | 13 | <dimen name="menu_width">256dp</dimen> |
| 14 | <dimen name="card_width">165dp</dimen> | 14 | <dimen name="card_width">165dp</dimen> |
| 15 | <dimen name="icon_inset">24dp</dimen> | ||
| 15 | 16 | ||
| 16 | <dimen name="dialog_margin">20dp</dimen> | 17 | <dimen name="dialog_margin">20dp</dimen> |
| 17 | <dimen name="elevated_app_bar">3dp</dimen> | 18 | <dimen name="elevated_app_bar">3dp</dimen> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 02e25504d..b163e6fc1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -29,6 +29,7 @@ | |||
| 29 | <string name="back">Back</string> | 29 | <string name="back">Back</string> |
| 30 | <string name="add_games">Add Games</string> | 30 | <string name="add_games">Add Games</string> |
| 31 | <string name="add_games_description">Select your games folder</string> | 31 | <string name="add_games_description">Select your games folder</string> |
| 32 | <string name="step_complete">Complete!</string> | ||
| 32 | 33 | ||
| 33 | <!-- Home strings --> | 34 | <!-- Home strings --> |
| 34 | <string name="home_games">Games</string> | 35 | <string name="home_games">Games</string> |
| @@ -42,6 +43,7 @@ | |||
| 42 | <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> | 43 | <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> |
| 43 | <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> | 44 | <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> |
| 44 | <string name="home_search_games">Search games</string> | 45 | <string name="home_search_games">Search games</string> |
| 46 | <string name="search_settings">Search settings</string> | ||
| 45 | <string name="games_dir_selected">Games directory selected</string> | 47 | <string name="games_dir_selected">Games directory selected</string> |
| 46 | <string name="install_prod_keys">Install prod.keys</string> | 48 | <string name="install_prod_keys">Install prod.keys</string> |
| 47 | <string name="install_prod_keys_description">Required to decrypt retail games</string> | 49 | <string name="install_prod_keys_description">Required to decrypt retail games</string> |
| @@ -73,6 +75,7 @@ | |||
| 73 | <string name="install_gpu_driver">Install GPU driver</string> | 75 | <string name="install_gpu_driver">Install GPU driver</string> |
| 74 | <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> | 76 | <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> |
| 75 | <string name="advanced_settings">Advanced settings</string> | 77 | <string name="advanced_settings">Advanced settings</string> |
| 78 | <string name="advanced_settings_game">Advanced settings: %1$s</string> | ||
| 76 | <string name="settings_description">Configure emulator settings</string> | 79 | <string name="settings_description">Configure emulator settings</string> |
| 77 | <string name="search_recently_played">Recently played</string> | 80 | <string name="search_recently_played">Recently played</string> |
| 78 | <string name="search_recently_added">Recently added</string> | 81 | <string name="search_recently_added">Recently added</string> |
| @@ -149,6 +152,7 @@ | |||
| 149 | <string name="frame_limit_slider">Limit speed percent</string> | 152 | <string name="frame_limit_slider">Limit speed percent</string> |
| 150 | <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string> | 153 | <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string> |
| 151 | <string name="cpu_accuracy">CPU accuracy</string> | 154 | <string name="cpu_accuracy">CPU accuracy</string> |
| 155 | <string name="value_with_units">%1$s%2$s</string> | ||
| 152 | 156 | ||
| 153 | <!-- System settings strings --> | 157 | <!-- System settings strings --> |
| 154 | <string name="use_docked_mode">Docked Mode</string> | 158 | <string name="use_docked_mode">Docked Mode</string> |
| @@ -198,7 +202,9 @@ | |||
| 198 | <string name="ini_saved">Saved settings</string> | 202 | <string name="ini_saved">Saved settings</string> |
| 199 | <string name="gameid_saved">Saved settings for %1$s</string> | 203 | <string name="gameid_saved">Saved settings for %1$s</string> |
| 200 | <string name="error_saving">Error saving %1$s.ini: %2$s</string> | 204 | <string name="error_saving">Error saving %1$s.ini: %2$s</string> |
| 205 | <string name="unimplemented_menu">Unimplemented Menu</string> | ||
| 201 | <string name="loading">Loading…</string> | 206 | <string name="loading">Loading…</string> |
| 207 | <string name="shutting_down">Shutting down…</string> | ||
| 202 | <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> | 208 | <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> |
| 203 | <string name="reset_to_default">Reset to default</string> | 209 | <string name="reset_to_default">Reset to default</string> |
| 204 | <string name="reset_all_settings">Reset all settings?</string> | 210 | <string name="reset_all_settings">Reset all settings?</string> |
| @@ -257,7 +263,6 @@ | |||
| 257 | <string name="emulation_pause">Pause emulation</string> | 263 | <string name="emulation_pause">Pause emulation</string> |
| 258 | <string name="emulation_unpause">Unpause emulation</string> | 264 | <string name="emulation_unpause">Unpause emulation</string> |
| 259 | <string name="emulation_input_overlay">Overlay options</string> | 265 | <string name="emulation_input_overlay">Overlay options</string> |
| 260 | <string name="emulation_game_loading">Game loading…</string> | ||
| 261 | 266 | ||
| 262 | <string name="load_settings">Loading settings…</string> | 267 | <string name="load_settings">Loading settings…</string> |
| 263 | 268 | ||
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index e7b595459..400988c5f 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt | |||
| @@ -2,6 +2,21 @@ | |||
| 2 | # SPDX-License-Identifier: GPL-2.0-or-later | 2 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | add_library(audio_core STATIC | 4 | add_library(audio_core STATIC |
| 5 | adsp/adsp.cpp | ||
| 6 | adsp/adsp.h | ||
| 7 | adsp/mailbox.h | ||
| 8 | adsp/apps/audio_renderer/audio_renderer.cpp | ||
| 9 | adsp/apps/audio_renderer/audio_renderer.h | ||
| 10 | adsp/apps/audio_renderer/command_buffer.h | ||
| 11 | adsp/apps/audio_renderer/command_list_processor.cpp | ||
| 12 | adsp/apps/audio_renderer/command_list_processor.h | ||
| 13 | adsp/apps/opus/opus_decoder.cpp | ||
| 14 | adsp/apps/opus/opus_decoder.h | ||
| 15 | adsp/apps/opus/opus_decode_object.cpp | ||
| 16 | adsp/apps/opus/opus_decode_object.h | ||
| 17 | adsp/apps/opus/opus_multistream_decode_object.cpp | ||
| 18 | adsp/apps/opus/opus_multistream_decode_object.h | ||
| 19 | adsp/apps/opus/shared_memory.h | ||
| 5 | audio_core.cpp | 20 | audio_core.cpp |
| 6 | audio_core.h | 21 | audio_core.h |
| 7 | audio_event.h | 22 | audio_event.h |
| @@ -27,18 +42,18 @@ add_library(audio_core STATIC | |||
| 27 | in/audio_in.h | 42 | in/audio_in.h |
| 28 | in/audio_in_system.cpp | 43 | in/audio_in_system.cpp |
| 29 | in/audio_in_system.h | 44 | in/audio_in_system.h |
| 45 | opus/hardware_opus.cpp | ||
| 46 | opus/hardware_opus.h | ||
| 47 | opus/decoder_manager.cpp | ||
| 48 | opus/decoder_manager.h | ||
| 49 | opus/decoder.cpp | ||
| 50 | opus/decoder.h | ||
| 51 | opus/parameters.h | ||
| 30 | out/audio_out.cpp | 52 | out/audio_out.cpp |
| 31 | out/audio_out.h | 53 | out/audio_out.h |
| 32 | out/audio_out_system.cpp | 54 | out/audio_out_system.cpp |
| 33 | out/audio_out_system.h | 55 | out/audio_out_system.h |
| 34 | precompiled_headers.h | 56 | precompiled_headers.h |
| 35 | renderer/adsp/adsp.cpp | ||
| 36 | renderer/adsp/adsp.h | ||
| 37 | renderer/adsp/audio_renderer.cpp | ||
| 38 | renderer/adsp/audio_renderer.h | ||
| 39 | renderer/adsp/command_buffer.h | ||
| 40 | renderer/adsp/command_list_processor.cpp | ||
| 41 | renderer/adsp/command_list_processor.h | ||
| 42 | renderer/audio_device.cpp | 57 | renderer/audio_device.cpp |
| 43 | renderer/audio_device.h | 58 | renderer/audio_device.h |
| 44 | renderer/audio_renderer.h | 59 | renderer/audio_renderer.h |
| @@ -213,7 +228,7 @@ else() | |||
| 213 | ) | 228 | ) |
| 214 | endif() | 229 | endif() |
| 215 | 230 | ||
| 216 | target_link_libraries(audio_core PUBLIC common core) | 231 | target_link_libraries(audio_core PUBLIC common core Opus::opus) |
| 217 | if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) | 232 | if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) |
| 218 | target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) | 233 | target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) |
| 219 | endif() | 234 | endif() |
diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp new file mode 100644 index 000000000..6c53c98fd --- /dev/null +++ b/src/audio_core/adsp/adsp.cpp | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/adsp.h" | ||
| 5 | #include "core/core.h" | ||
| 6 | |||
| 7 | namespace AudioCore::ADSP { | ||
| 8 | |||
| 9 | ADSP::ADSP(Core::System& system, Sink::Sink& sink) { | ||
| 10 | audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink); | ||
| 11 | opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system); | ||
| 12 | opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start); | ||
| 13 | if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) { | ||
| 14 | LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize."); | ||
| 15 | return; | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { | ||
| 20 | return *audio_renderer.get(); | ||
| 21 | } | ||
| 22 | |||
| 23 | OpusDecoder::OpusDecoder& ADSP::OpusDecoder() { | ||
| 24 | return *opus_decoder.get(); | ||
| 25 | } | ||
| 26 | |||
| 27 | } // namespace AudioCore::ADSP | ||
diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h new file mode 100644 index 000000000..a0c24a16a --- /dev/null +++ b/src/audio_core/adsp/adsp.h | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" | ||
| 7 | #include "audio_core/adsp/apps/opus/opus_decoder.h" | ||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace Core { | ||
| 11 | class System; | ||
| 12 | } // namespace Core | ||
| 13 | |||
| 14 | namespace AudioCore { | ||
| 15 | namespace Sink { | ||
| 16 | class Sink; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace ADSP { | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Represents the ADSP embedded within the audio sysmodule. | ||
| 23 | * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot. | ||
| 24 | * | ||
| 25 | * The kernel will run the apps you write for it, Nintendo have the following: | ||
| 26 | * | ||
| 27 | * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all | ||
| 28 | * audio samples end up, and we skip it entirely, since we have very different backends and | ||
| 29 | * mixing is implicitly handled by the OS (but also due to lack of research/simplicity). | ||
| 30 | * | ||
| 31 | * AudioRenderer - Receives command lists generated by the audio render | ||
| 32 | * system on the host, processes them, and sends the samples to Gmix. | ||
| 33 | * | ||
| 34 | * OpusDecoder - Contains libopus, and decodes Opus audio packets into raw pcm data. | ||
| 35 | * | ||
| 36 | * Communication between the host and ADSP is done through mailboxes, and mapping of shared memory. | ||
| 37 | */ | ||
| 38 | class ADSP { | ||
| 39 | public: | ||
| 40 | explicit ADSP(Core::System& system, Sink::Sink& sink); | ||
| 41 | ~ADSP() = default; | ||
| 42 | |||
| 43 | AudioRenderer::AudioRenderer& AudioRenderer(); | ||
| 44 | OpusDecoder::OpusDecoder& OpusDecoder(); | ||
| 45 | |||
| 46 | private: | ||
| 47 | /// AudioRenderer app | ||
| 48 | std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{}; | ||
| 49 | std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{}; | ||
| 50 | }; | ||
| 51 | |||
| 52 | } // namespace ADSP | ||
| 53 | } // namespace AudioCore | ||
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp new file mode 100644 index 000000000..972d5e45b --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp | |||
| @@ -0,0 +1,218 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <chrono> | ||
| 6 | |||
| 7 | #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" | ||
| 8 | #include "audio_core/audio_core.h" | ||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/sink/sink.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | #include "common/microprofile.h" | ||
| 13 | #include "common/thread.h" | ||
| 14 | #include "core/core.h" | ||
| 15 | #include "core/core_timing.h" | ||
| 16 | |||
| 17 | MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97)); | ||
| 18 | |||
| 19 | namespace AudioCore::ADSP::AudioRenderer { | ||
| 20 | |||
| 21 | AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_) | ||
| 22 | : system{system_}, sink{sink_} {} | ||
| 23 | |||
| 24 | AudioRenderer::~AudioRenderer() { | ||
| 25 | Stop(); | ||
| 26 | } | ||
| 27 | |||
| 28 | void AudioRenderer::Start() { | ||
| 29 | CreateSinkStreams(); | ||
| 30 | |||
| 31 | mailbox.Initialize(AppMailboxId::AudioRenderer); | ||
| 32 | |||
| 33 | main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); }); | ||
| 34 | |||
| 35 | mailbox.Send(Direction::DSP, Message::InitializeOK); | ||
| 36 | if (mailbox.Receive(Direction::Host) != Message::InitializeOK) { | ||
| 37 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | ||
| 38 | "message response from ADSP!"); | ||
| 39 | return; | ||
| 40 | } | ||
| 41 | running = true; | ||
| 42 | } | ||
| 43 | |||
| 44 | void AudioRenderer::Stop() { | ||
| 45 | if (!running) { | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | mailbox.Send(Direction::DSP, Message::Shutdown); | ||
| 50 | if (mailbox.Receive(Direction::Host) != Message::Shutdown) { | ||
| 51 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | ||
| 52 | "message response from ADSP!"); | ||
| 53 | } | ||
| 54 | main_thread.request_stop(); | ||
| 55 | main_thread.join(); | ||
| 56 | |||
| 57 | for (auto& stream : streams) { | ||
| 58 | if (stream) { | ||
| 59 | stream->Stop(); | ||
| 60 | sink.CloseStream(stream); | ||
| 61 | stream = nullptr; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | running = false; | ||
| 65 | } | ||
| 66 | |||
| 67 | void AudioRenderer::Signal() { | ||
| 68 | signalled_tick = system.CoreTiming().GetGlobalTimeNs().count(); | ||
| 69 | Send(Direction::DSP, Message::Render); | ||
| 70 | } | ||
| 71 | |||
| 72 | void AudioRenderer::Wait() { | ||
| 73 | auto msg = Receive(Direction::Host); | ||
| 74 | if (msg != Message::RenderResponse) { | ||
| 75 | LOG_ERROR(Service_Audio, | ||
| 76 | "Did not receive the expected render response from the AudioRenderer! Expected " | ||
| 77 | "{}, got {}", | ||
| 78 | Message::RenderResponse, msg); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | void AudioRenderer::Send(Direction dir, u32 message) { | ||
| 83 | mailbox.Send(dir, std::move(message)); | ||
| 84 | } | ||
| 85 | |||
| 86 | u32 AudioRenderer::Receive(Direction dir) { | ||
| 87 | return mailbox.Receive(dir); | ||
| 88 | } | ||
| 89 | |||
| 90 | void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, | ||
| 91 | u64 applet_resource_user_id, bool reset) noexcept { | ||
| 92 | command_buffers[session_id].buffer = buffer; | ||
| 93 | command_buffers[session_id].size = size; | ||
| 94 | command_buffers[session_id].time_limit = time_limit; | ||
| 95 | command_buffers[session_id].applet_resource_user_id = applet_resource_user_id; | ||
| 96 | command_buffers[session_id].reset_buffer = reset; | ||
| 97 | } | ||
| 98 | |||
| 99 | u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { | ||
| 100 | return command_buffers[session_id].remaining_command_count; | ||
| 101 | } | ||
| 102 | |||
| 103 | void AudioRenderer::ClearRemainCommandCount(s32 session_id) noexcept { | ||
| 104 | command_buffers[session_id].remaining_command_count = 0; | ||
| 105 | } | ||
| 106 | |||
| 107 | u64 AudioRenderer::GetRenderingStartTick(s32 session_id) const noexcept { | ||
| 108 | return (1000 * command_buffers[session_id].render_time_taken_us) + signalled_tick; | ||
| 109 | } | ||
| 110 | |||
| 111 | void AudioRenderer::CreateSinkStreams() { | ||
| 112 | u32 channels{sink.GetDeviceChannels()}; | ||
| 113 | for (u32 i = 0; i < MaxRendererSessions; i++) { | ||
| 114 | std::string name{fmt::format("ADSP_RenderStream-{}", i)}; | ||
| 115 | streams[i] = | ||
| 116 | sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); | ||
| 117 | streams[i]->SetRingSize(4); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | void AudioRenderer::Main(std::stop_token stop_token) { | ||
| 122 | static constexpr char name[]{"DSP_AudioRenderer_Main"}; | ||
| 123 | MicroProfileOnThreadCreate(name); | ||
| 124 | Common::SetCurrentThreadName(name); | ||
| 125 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | ||
| 126 | |||
| 127 | // TODO: Create buffer map/unmap thread + mailbox | ||
| 128 | // TODO: Create gMix devices, initialize them here | ||
| 129 | |||
| 130 | if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) { | ||
| 131 | LOG_ERROR(Service_Audio, | ||
| 132 | "ADSP Audio Renderer -- Failed to receive initialize message from host!"); | ||
| 133 | return; | ||
| 134 | } | ||
| 135 | |||
| 136 | mailbox.Send(Direction::Host, Message::InitializeOK); | ||
| 137 | |||
| 138 | // 0.12 seconds (2,304,000 / 19,200,000) | ||
| 139 | constexpr u64 max_process_time{2'304'000ULL}; | ||
| 140 | |||
| 141 | while (!stop_token.stop_requested()) { | ||
| 142 | auto msg{mailbox.Receive(Direction::DSP)}; | ||
| 143 | switch (msg) { | ||
| 144 | case Message::Shutdown: | ||
| 145 | mailbox.Send(Direction::Host, Message::Shutdown); | ||
| 146 | return; | ||
| 147 | |||
| 148 | case Message::Render: { | ||
| 149 | if (system.IsShuttingDown()) [[unlikely]] { | ||
| 150 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); | ||
| 151 | mailbox.Send(Direction::Host, Message::RenderResponse); | ||
| 152 | continue; | ||
| 153 | } | ||
| 154 | std::array<bool, MaxRendererSessions> buffers_reset{}; | ||
| 155 | std::array<u64, MaxRendererSessions> render_times_taken{}; | ||
| 156 | const auto start_time{system.CoreTiming().GetGlobalTimeUs().count()}; | ||
| 157 | |||
| 158 | for (u32 index = 0; index < MaxRendererSessions; index++) { | ||
| 159 | auto& command_buffer{command_buffers[index]}; | ||
| 160 | auto& command_list_processor{command_list_processors[index]}; | ||
| 161 | |||
| 162 | // Check this buffer is valid, as it may not be used. | ||
| 163 | if (command_buffer.buffer != 0) { | ||
| 164 | // If there are no remaining commands (from the previous list), | ||
| 165 | // this is a new command list, initialize it. | ||
| 166 | if (command_buffer.remaining_command_count == 0) { | ||
| 167 | command_list_processor.Initialize(system, command_buffer.buffer, | ||
| 168 | command_buffer.size, streams[index]); | ||
| 169 | } | ||
| 170 | |||
| 171 | if (command_buffer.reset_buffer && !buffers_reset[index]) { | ||
| 172 | streams[index]->ClearQueue(); | ||
| 173 | buffers_reset[index] = true; | ||
| 174 | } | ||
| 175 | |||
| 176 | u64 max_time{max_process_time}; | ||
| 177 | if (index == 1 && command_buffer.applet_resource_user_id == | ||
| 178 | command_buffers[0].applet_resource_user_id) { | ||
| 179 | max_time = max_process_time - render_times_taken[0]; | ||
| 180 | if (render_times_taken[0] > max_process_time) { | ||
| 181 | max_time = 0; | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | max_time = std::min(command_buffer.time_limit, max_time); | ||
| 186 | command_list_processor.SetProcessTimeMax(max_time); | ||
| 187 | |||
| 188 | if (index == 0) { | ||
| 189 | streams[index]->WaitFreeSpace(stop_token); | ||
| 190 | } | ||
| 191 | |||
| 192 | // Process the command list | ||
| 193 | { | ||
| 194 | MICROPROFILE_SCOPE(Audio_Renderer); | ||
| 195 | render_times_taken[index] = | ||
| 196 | command_list_processor.Process(index) - start_time; | ||
| 197 | } | ||
| 198 | |||
| 199 | const auto end_time{system.CoreTiming().GetGlobalTimeUs().count()}; | ||
| 200 | |||
| 201 | command_buffer.remaining_command_count = | ||
| 202 | command_list_processor.GetRemainingCommandCount(); | ||
| 203 | command_buffer.render_time_taken_us = end_time - start_time; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | mailbox.Send(Direction::Host, Message::RenderResponse); | ||
| 208 | } break; | ||
| 209 | |||
| 210 | default: | ||
| 211 | LOG_WARNING(Service_Audio, | ||
| 212 | "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg); | ||
| 213 | break; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | } // namespace AudioCore::ADSP::AudioRenderer | ||
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h new file mode 100644 index 000000000..85874d88a --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h | |||
| @@ -0,0 +1,109 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <thread> | ||
| 9 | |||
| 10 | #include "audio_core/adsp/apps/audio_renderer/command_buffer.h" | ||
| 11 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" | ||
| 12 | #include "audio_core/adsp/mailbox.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | #include "common/polyfill_thread.h" | ||
| 15 | #include "common/reader_writer_queue.h" | ||
| 16 | #include "common/thread.h" | ||
| 17 | |||
| 18 | namespace Core { | ||
| 19 | class System; | ||
| 20 | } // namespace Core | ||
| 21 | |||
| 22 | namespace AudioCore { | ||
| 23 | namespace Sink { | ||
| 24 | class Sink; | ||
| 25 | } | ||
| 26 | |||
| 27 | namespace ADSP::AudioRenderer { | ||
| 28 | |||
| 29 | enum Message : u32 { | ||
| 30 | Invalid = 0, | ||
| 31 | MapUnmap_Map = 1, | ||
| 32 | MapUnmap_MapResponse = 2, | ||
| 33 | MapUnmap_Unmap = 3, | ||
| 34 | MapUnmap_UnmapResponse = 4, | ||
| 35 | MapUnmap_InvalidateCache = 5, | ||
| 36 | MapUnmap_InvalidateCacheResponse = 6, | ||
| 37 | MapUnmap_Shutdown = 7, | ||
| 38 | MapUnmap_ShutdownResponse = 8, | ||
| 39 | InitializeOK = 22, | ||
| 40 | RenderResponse = 32, | ||
| 41 | Render = 42, | ||
| 42 | Shutdown = 52, | ||
| 43 | }; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * The AudioRenderer application running on the ADSP. | ||
| 47 | */ | ||
| 48 | class AudioRenderer { | ||
| 49 | public: | ||
| 50 | explicit AudioRenderer(Core::System& system, Sink::Sink& sink); | ||
| 51 | ~AudioRenderer(); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Start the AudioRenderer. | ||
| 55 | * | ||
| 56 | * @param mailbox The mailbox to use for this session. | ||
| 57 | */ | ||
| 58 | void Start(); | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Stop the AudioRenderer. | ||
| 62 | */ | ||
| 63 | void Stop(); | ||
| 64 | |||
| 65 | void Signal(); | ||
| 66 | void Wait(); | ||
| 67 | |||
| 68 | void Send(Direction dir, u32 message); | ||
| 69 | u32 Receive(Direction dir); | ||
| 70 | |||
| 71 | void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, | ||
| 72 | u64 applet_resource_user_id, bool reset) noexcept; | ||
| 73 | u32 GetRemainCommandCount(s32 session_id) const noexcept; | ||
| 74 | void ClearRemainCommandCount(s32 session_id) noexcept; | ||
| 75 | u64 GetRenderingStartTick(s32 session_id) const noexcept; | ||
| 76 | |||
| 77 | private: | ||
| 78 | /** | ||
| 79 | * Main AudioRenderer thread, responsible for processing the command lists. | ||
| 80 | */ | ||
| 81 | void Main(std::stop_token stop_token); | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Creates the streams which will receive the processed samples. | ||
| 85 | */ | ||
| 86 | void CreateSinkStreams(); | ||
| 87 | |||
| 88 | /// Core system | ||
| 89 | Core::System& system; | ||
| 90 | /// The output sink the AudioRenderer will send samples to | ||
| 91 | Sink::Sink& sink; | ||
| 92 | /// The active mailbox | ||
| 93 | Mailbox mailbox; | ||
| 94 | /// Main thread | ||
| 95 | std::jthread main_thread{}; | ||
| 96 | /// The current state | ||
| 97 | std::atomic<bool> running{}; | ||
| 98 | /// Shared memory of input command buffers, set by host, read by DSP | ||
| 99 | std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; | ||
| 100 | /// The command lists to process | ||
| 101 | std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; | ||
| 102 | /// The streams which will receive the processed samples | ||
| 103 | std::array<Sink::SinkStream*, MaxRendererSessions> streams{}; | ||
| 104 | /// CPU Tick when the DSP was signalled to process, uses time rather than tick | ||
| 105 | u64 signalled_tick{0}; | ||
| 106 | }; | ||
| 107 | |||
| 108 | } // namespace ADSP::AudioRenderer | ||
| 109 | } // namespace AudioCore | ||
diff --git a/src/audio_core/adsp/apps/audio_renderer/command_buffer.h b/src/audio_core/adsp/apps/audio_renderer/command_buffer.h new file mode 100644 index 000000000..3fd1b09dc --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/command_buffer.h | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/common/common.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::ADSP::AudioRenderer { | ||
| 10 | |||
| 11 | struct CommandBuffer { | ||
| 12 | // Set by the host | ||
| 13 | CpuAddr buffer{}; | ||
| 14 | u64 size{}; | ||
| 15 | u64 time_limit{}; | ||
| 16 | u64 applet_resource_user_id{}; | ||
| 17 | bool reset_buffer{}; | ||
| 18 | // Set by the DSP | ||
| 19 | u32 remaining_command_count{}; | ||
| 20 | u64 render_time_taken_us{}; | ||
| 21 | }; | ||
| 22 | |||
| 23 | } // namespace AudioCore::ADSP::AudioRenderer | ||
diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp index 3a0f1ae38..24e4d0496 100644 --- a/src/audio_core/renderer/adsp/command_list_processor.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp | |||
| @@ -1,9 +1,9 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <string> | 4 | #include <string> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/command_list_header.h" | 7 | #include "audio_core/renderer/command/command_list_header.h" |
| 8 | #include "audio_core/renderer/command/commands.h" | 8 | #include "audio_core/renderer/command/commands.h" |
| 9 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| @@ -11,15 +11,15 @@ | |||
| 11 | #include "core/core_timing.h" | 11 | #include "core/core_timing.h" |
| 12 | #include "core/memory.h" | 12 | #include "core/memory.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer::ADSP { | 14 | namespace AudioCore::ADSP::AudioRenderer { |
| 15 | 15 | ||
| 16 | void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, | 16 | void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, |
| 17 | Sink::SinkStream* stream_) { | 17 | Sink::SinkStream* stream_) { |
| 18 | system = &system_; | 18 | system = &system_; |
| 19 | memory = &system->ApplicationMemory(); | 19 | memory = &system->ApplicationMemory(); |
| 20 | stream = stream_; | 20 | stream = stream_; |
| 21 | header = reinterpret_cast<CommandListHeader*>(buffer); | 21 | header = reinterpret_cast<Renderer::CommandListHeader*>(buffer); |
| 22 | commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | 22 | commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader)); |
| 23 | commands_buffer_size = size; | 23 | commands_buffer_size = size; |
| 24 | command_count = header->command_count; | 24 | command_count = header->command_count; |
| 25 | sample_count = header->sample_count; | 25 | sample_count = header->sample_count; |
| @@ -37,17 +37,12 @@ u32 CommandListProcessor::GetRemainingCommandCount() const { | |||
| 37 | return command_count - processed_command_count; | 37 | return command_count - processed_command_count; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { | ||
| 41 | commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | ||
| 42 | commands_buffer_size = size; | ||
| 43 | } | ||
| 44 | |||
| 45 | Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { | 40 | Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { |
| 46 | return stream; | 41 | return stream; |
| 47 | } | 42 | } |
| 48 | 43 | ||
| 49 | u64 CommandListProcessor::Process(u32 session_id) { | 44 | u64 CommandListProcessor::Process(u32 session_id) { |
| 50 | const auto start_time_{system->CoreTiming().GetClockTicks()}; | 45 | const auto start_time_{system->CoreTiming().GetGlobalTimeUs().count()}; |
| 51 | const auto command_base{CpuAddr(commands)}; | 46 | const auto command_base{CpuAddr(commands)}; |
| 52 | 47 | ||
| 53 | if (processed_command_count > 0) { | 48 | if (processed_command_count > 0) { |
| @@ -60,12 +55,12 @@ u64 CommandListProcessor::Process(u32 session_id) { | |||
| 60 | std::string dump{fmt::format("\nSession {}\n", session_id)}; | 55 | std::string dump{fmt::format("\nSession {}\n", session_id)}; |
| 61 | 56 | ||
| 62 | for (u32 index = 0; index < command_count; index++) { | 57 | for (u32 index = 0; index < command_count; index++) { |
| 63 | auto& command{*reinterpret_cast<ICommand*>(commands)}; | 58 | auto& command{*reinterpret_cast<Renderer::ICommand*>(commands)}; |
| 64 | 59 | ||
| 65 | if (command.magic != 0xCAFEBABE) { | 60 | if (command.magic != 0xCAFEBABE) { |
| 66 | LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", | 61 | LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", |
| 67 | command.magic); | 62 | command.magic); |
| 68 | return system->CoreTiming().GetClockTicks() - start_time_; | 63 | return system->CoreTiming().GetGlobalTimeUs().count() - start_time_; |
| 69 | } | 64 | } |
| 70 | 65 | ||
| 71 | auto current_offset{CpuAddr(commands) - command_base}; | 66 | auto current_offset{CpuAddr(commands) - command_base}; |
| @@ -74,8 +69,8 @@ u64 CommandListProcessor::Process(u32 session_id) { | |||
| 74 | LOG_ERROR(Service_Audio, | 69 | LOG_ERROR(Service_Audio, |
| 75 | "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", | 70 | "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", |
| 76 | commands_buffer_size, | 71 | commands_buffer_size, |
| 77 | CpuAddr(commands) + command.size - sizeof(CommandListHeader)); | 72 | CpuAddr(commands) + command.size - sizeof(Renderer::CommandListHeader)); |
| 78 | return system->CoreTiming().GetClockTicks() - start_time_; | 73 | return system->CoreTiming().GetGlobalTimeUs().count() - start_time_; |
| 79 | } | 74 | } |
| 80 | 75 | ||
| 81 | if (Settings::values.dump_audio_commands) { | 76 | if (Settings::values.dump_audio_commands) { |
| @@ -101,8 +96,8 @@ u64 CommandListProcessor::Process(u32 session_id) { | |||
| 101 | last_dump = dump; | 96 | last_dump = dump; |
| 102 | } | 97 | } |
| 103 | 98 | ||
| 104 | end_time = system->CoreTiming().GetClockTicks(); | 99 | end_time = system->CoreTiming().GetGlobalTimeUs().count(); |
| 105 | return end_time - start_time_; | 100 | return end_time - start_time_; |
| 106 | } | 101 | } |
| 107 | 102 | ||
| 108 | } // namespace AudioCore::AudioRenderer::ADSP | 103 | } // namespace AudioCore::ADSP::AudioRenderer |
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h index d78269e1d..4e5fb793e 100644 --- a/src/audio_core/renderer/adsp/command_list_processor.h +++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <span> | 6 | #include <span> |
| 7 | 7 | ||
| 8 | #include "audio_core/common/common.h" | 8 | #include "audio_core/common/common.h" |
| 9 | #include "audio_core/renderer/command/command_list_header.h" | ||
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 10 | 11 | ||
| 11 | namespace Core { | 12 | namespace Core { |
| @@ -20,10 +21,11 @@ namespace Sink { | |||
| 20 | class SinkStream; | 21 | class SinkStream; |
| 21 | } | 22 | } |
| 22 | 23 | ||
| 23 | namespace AudioRenderer { | 24 | namespace Renderer { |
| 24 | struct CommandListHeader; | 25 | struct CommandListHeader; |
| 26 | } | ||
| 25 | 27 | ||
| 26 | namespace ADSP { | 28 | namespace ADSP::AudioRenderer { |
| 27 | 29 | ||
| 28 | /** | 30 | /** |
| 29 | * A processor for command lists given to the AudioRenderer. | 31 | * A processor for command lists given to the AudioRenderer. |
| @@ -55,14 +57,6 @@ public: | |||
| 55 | u32 GetRemainingCommandCount() const; | 57 | u32 GetRemainingCommandCount() const; |
| 56 | 58 | ||
| 57 | /** | 59 | /** |
| 58 | * Set the command buffer. | ||
| 59 | * | ||
| 60 | * @param buffer - The buffer to use. | ||
| 61 | * @param size - The size of the buffer. | ||
| 62 | */ | ||
| 63 | void SetBuffer(CpuAddr buffer, u64 size); | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Get the stream for this command list. | 60 | * Get the stream for this command list. |
| 67 | * | 61 | * |
| 68 | * @return The stream associated with this command list. | 62 | * @return The stream associated with this command list. |
| @@ -85,7 +79,7 @@ public: | |||
| 85 | /// Stream for the processed samples | 79 | /// Stream for the processed samples |
| 86 | Sink::SinkStream* stream{}; | 80 | Sink::SinkStream* stream{}; |
| 87 | /// Header info for this command list | 81 | /// Header info for this command list |
| 88 | CommandListHeader* header{}; | 82 | Renderer::CommandListHeader* header{}; |
| 89 | /// The command buffer | 83 | /// The command buffer |
| 90 | u8* commands{}; | 84 | u8* commands{}; |
| 91 | /// The command buffer size | 85 | /// The command buffer size |
| @@ -114,6 +108,5 @@ public: | |||
| 114 | std::string last_dump{}; | 108 | std::string last_dump{}; |
| 115 | }; | 109 | }; |
| 116 | 110 | ||
| 117 | } // namespace ADSP | 111 | } // namespace ADSP::AudioRenderer |
| 118 | } // namespace AudioRenderer | ||
| 119 | } // namespace AudioCore | 112 | } // namespace AudioCore |
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp new file mode 100644 index 000000000..2c16d3769 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/apps/opus/opus_decode_object.h" | ||
| 5 | #include "common/assert.h" | ||
| 6 | |||
| 7 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 8 | namespace { | ||
| 9 | bool IsValidChannelCount(u32 channel_count) { | ||
| 10 | return channel_count == 1 || channel_count == 2; | ||
| 11 | } | ||
| 12 | } // namespace | ||
| 13 | |||
| 14 | u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) { | ||
| 15 | if (!IsValidChannelCount(channel_count)) { | ||
| 16 | return 0; | ||
| 17 | } | ||
| 18 | return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count); | ||
| 19 | } | ||
| 20 | |||
| 21 | OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) { | ||
| 22 | auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer); | ||
| 23 | auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2); | ||
| 24 | |||
| 25 | if (new_decoder->magic == DecodeObjectMagic) { | ||
| 26 | if (!new_decoder->initialized || | ||
| 27 | (new_decoder->initialized && new_decoder->self == comparison)) { | ||
| 28 | new_decoder->state_valid = true; | ||
| 29 | } | ||
| 30 | } else { | ||
| 31 | new_decoder->initialized = false; | ||
| 32 | new_decoder->state_valid = true; | ||
| 33 | } | ||
| 34 | return *new_decoder; | ||
| 35 | } | ||
| 36 | |||
| 37 | s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) { | ||
| 38 | if (!state_valid) { | ||
| 39 | return OPUS_INVALID_STATE; | ||
| 40 | } | ||
| 41 | |||
| 42 | if (initialized) { | ||
| 43 | return OPUS_OK; | ||
| 44 | } | ||
| 45 | |||
| 46 | // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include | ||
| 47 | // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer | ||
| 48 | // provided. | ||
| 49 | // We could use _create and have libopus allocate it for us, but then we have to separately | ||
| 50 | // track which decoder is being used between this and multistream in order to call the correct | ||
| 51 | // destroy from the host side. | ||
| 52 | // This is a bit cringe, but is safe as these objects are only ever initialized inside the given | ||
| 53 | // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow. | ||
| 54 | decoder = (LibOpusDecoder*)(this + 1); | ||
| 55 | s32 ret = opus_decoder_init(decoder, sample_rate, channel_count); | ||
| 56 | if (ret == OPUS_OK) { | ||
| 57 | magic = DecodeObjectMagic; | ||
| 58 | initialized = true; | ||
| 59 | state_valid = true; | ||
| 60 | self = this; | ||
| 61 | final_range = 0; | ||
| 62 | } | ||
| 63 | return ret; | ||
| 64 | } | ||
| 65 | |||
| 66 | s32 OpusDecodeObject::Shutdown() { | ||
| 67 | if (!state_valid) { | ||
| 68 | return OPUS_INVALID_STATE; | ||
| 69 | } | ||
| 70 | |||
| 71 | if (initialized) { | ||
| 72 | magic = 0x0; | ||
| 73 | initialized = false; | ||
| 74 | state_valid = false; | ||
| 75 | self = nullptr; | ||
| 76 | final_range = 0; | ||
| 77 | decoder = nullptr; | ||
| 78 | } | ||
| 79 | return OPUS_OK; | ||
| 80 | } | ||
| 81 | |||
| 82 | s32 OpusDecodeObject::ResetDecoder() { | ||
| 83 | return opus_decoder_ctl(decoder, OPUS_RESET_STATE); | ||
| 84 | } | ||
| 85 | |||
| 86 | s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, | ||
| 87 | u64 input_data, u64 input_data_size) { | ||
| 88 | ASSERT(initialized); | ||
| 89 | out_sample_count = 0; | ||
| 90 | |||
| 91 | if (!state_valid) { | ||
| 92 | return OPUS_INVALID_STATE; | ||
| 93 | } | ||
| 94 | |||
| 95 | auto ret_code_or_samples = opus_decode( | ||
| 96 | decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size), | ||
| 97 | reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0); | ||
| 98 | |||
| 99 | if (ret_code_or_samples < OPUS_OK) { | ||
| 100 | return ret_code_or_samples; | ||
| 101 | } | ||
| 102 | |||
| 103 | out_sample_count = ret_code_or_samples; | ||
| 104 | return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); | ||
| 105 | } | ||
| 106 | |||
| 107 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h new file mode 100644 index 000000000..6425f987c --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <opus.h> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 11 | using LibOpusDecoder = ::OpusDecoder; | ||
| 12 | static constexpr u32 DecodeObjectMagic = 0xDEADBEEF; | ||
| 13 | |||
| 14 | class OpusDecodeObject { | ||
| 15 | public: | ||
| 16 | static u32 GetWorkBufferSize(u32 channel_count); | ||
| 17 | static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2); | ||
| 18 | |||
| 19 | s32 InitializeDecoder(u32 sample_rate, u32 channel_count); | ||
| 20 | s32 Shutdown(); | ||
| 21 | s32 ResetDecoder(); | ||
| 22 | s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, | ||
| 23 | u64 input_data_size); | ||
| 24 | u32 GetFinalRange() const noexcept { | ||
| 25 | return final_range; | ||
| 26 | } | ||
| 27 | |||
| 28 | private: | ||
| 29 | u32 magic; | ||
| 30 | bool initialized; | ||
| 31 | bool state_valid; | ||
| 32 | OpusDecodeObject* self; | ||
| 33 | u32 final_range; | ||
| 34 | LibOpusDecoder* decoder; | ||
| 35 | }; | ||
| 36 | static_assert(std::is_trivially_constructible_v<OpusDecodeObject>); | ||
| 37 | |||
| 38 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp new file mode 100644 index 000000000..2084de128 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp | |||
| @@ -0,0 +1,269 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <chrono> | ||
| 6 | |||
| 7 | #include "audio_core/adsp/apps/opus/opus_decode_object.h" | ||
| 8 | #include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" | ||
| 9 | #include "audio_core/adsp/apps/opus/shared_memory.h" | ||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | #include "audio_core/common/common.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | #include "common/microprofile.h" | ||
| 14 | #include "common/thread.h" | ||
| 15 | #include "core/core.h" | ||
| 16 | #include "core/core_timing.h" | ||
| 17 | |||
| 18 | MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97)); | ||
| 19 | |||
| 20 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | constexpr size_t OpusStreamCountMax = 255; | ||
| 24 | |||
| 25 | bool IsValidChannelCount(u32 channel_count) { | ||
| 26 | return channel_count == 1 || channel_count == 2; | ||
| 27 | } | ||
| 28 | |||
| 29 | bool IsValidMultiStreamChannelCount(u32 channel_count) { | ||
| 30 | return channel_count <= OpusStreamCountMax; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) { | ||
| 34 | return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 && | ||
| 35 | sterero_stream_count > 0 && sterero_stream_count <= total_stream_count; | ||
| 36 | } | ||
| 37 | } // namespace | ||
| 38 | |||
| 39 | OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} { | ||
| 40 | init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); }); | ||
| 41 | } | ||
| 42 | |||
| 43 | OpusDecoder::~OpusDecoder() { | ||
| 44 | if (!running) { | ||
| 45 | init_thread.request_stop(); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | // Shutdown the thread | ||
| 50 | Send(Direction::DSP, Message::Shutdown); | ||
| 51 | auto msg = Receive(Direction::Host); | ||
| 52 | ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", | ||
| 53 | Message::ShutdownOK, msg); | ||
| 54 | main_thread.request_stop(); | ||
| 55 | main_thread.join(); | ||
| 56 | running = false; | ||
| 57 | } | ||
| 58 | |||
| 59 | void OpusDecoder::Send(Direction dir, u32 message) { | ||
| 60 | mailbox.Send(dir, std::move(message)); | ||
| 61 | } | ||
| 62 | |||
| 63 | u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) { | ||
| 64 | return mailbox.Receive(dir, stop_token); | ||
| 65 | } | ||
| 66 | |||
| 67 | void OpusDecoder::Init(std::stop_token stop_token) { | ||
| 68 | Common::SetCurrentThreadName("DSP_OpusDecoder_Init"); | ||
| 69 | |||
| 70 | if (Receive(Direction::DSP, stop_token) != Message::Start) { | ||
| 71 | LOG_ERROR(Service_Audio, | ||
| 72 | "DSP OpusDecoder failed to receive Start message. Opus initialization failed."); | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | main_thread = std::jthread([this](std::stop_token st) { Main(st); }); | ||
| 76 | running = true; | ||
| 77 | Send(Direction::Host, Message::StartOK); | ||
| 78 | } | ||
| 79 | |||
| 80 | void OpusDecoder::Main(std::stop_token stop_token) { | ||
| 81 | Common::SetCurrentThreadName("DSP_OpusDecoder_Main"); | ||
| 82 | |||
| 83 | while (!stop_token.stop_requested()) { | ||
| 84 | auto msg = Receive(Direction::DSP, stop_token); | ||
| 85 | switch (msg) { | ||
| 86 | case Shutdown: | ||
| 87 | Send(Direction::Host, Message::ShutdownOK); | ||
| 88 | return; | ||
| 89 | |||
| 90 | case GetWorkBufferSize: { | ||
| 91 | auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]); | ||
| 92 | |||
| 93 | ASSERT(IsValidChannelCount(channel_count)); | ||
| 94 | |||
| 95 | shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count); | ||
| 96 | Send(Direction::Host, Message::GetWorkBufferSizeOK); | ||
| 97 | } break; | ||
| 98 | |||
| 99 | case InitializeDecodeObject: { | ||
| 100 | auto buffer = shared_memory->host_send_data[0]; | ||
| 101 | auto buffer_size = shared_memory->host_send_data[1]; | ||
| 102 | auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); | ||
| 103 | auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]); | ||
| 104 | |||
| 105 | ASSERT(sample_rate >= 0); | ||
| 106 | ASSERT(IsValidChannelCount(channel_count)); | ||
| 107 | ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count)); | ||
| 108 | |||
| 109 | auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); | ||
| 110 | shared_memory->dsp_return_data[0] = | ||
| 111 | decoder_object.InitializeDecoder(sample_rate, channel_count); | ||
| 112 | |||
| 113 | Send(Direction::Host, Message::InitializeDecodeObjectOK); | ||
| 114 | } break; | ||
| 115 | |||
| 116 | case ShutdownDecodeObject: { | ||
| 117 | auto buffer = shared_memory->host_send_data[0]; | ||
| 118 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 119 | |||
| 120 | auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); | ||
| 121 | shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); | ||
| 122 | |||
| 123 | Send(Direction::Host, Message::ShutdownDecodeObjectOK); | ||
| 124 | } break; | ||
| 125 | |||
| 126 | case DecodeInterleaved: { | ||
| 127 | auto start_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 128 | |||
| 129 | auto buffer = shared_memory->host_send_data[0]; | ||
| 130 | auto input_data = shared_memory->host_send_data[1]; | ||
| 131 | auto input_data_size = shared_memory->host_send_data[2]; | ||
| 132 | auto output_data = shared_memory->host_send_data[3]; | ||
| 133 | auto output_data_size = shared_memory->host_send_data[4]; | ||
| 134 | auto final_range = static_cast<u32>(shared_memory->host_send_data[5]); | ||
| 135 | auto reset_requested = shared_memory->host_send_data[6]; | ||
| 136 | |||
| 137 | u32 decoded_samples{0}; | ||
| 138 | |||
| 139 | auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); | ||
| 140 | s32 error_code{OPUS_OK}; | ||
| 141 | if (reset_requested) { | ||
| 142 | error_code = decoder_object.ResetDecoder(); | ||
| 143 | } | ||
| 144 | |||
| 145 | if (error_code == OPUS_OK) { | ||
| 146 | error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, | ||
| 147 | input_data, input_data_size); | ||
| 148 | } | ||
| 149 | |||
| 150 | if (error_code == OPUS_OK) { | ||
| 151 | if (final_range && decoder_object.GetFinalRange() != final_range) { | ||
| 152 | error_code = OPUS_INVALID_PACKET; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | auto end_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 157 | shared_memory->dsp_return_data[0] = error_code; | ||
| 158 | shared_memory->dsp_return_data[1] = decoded_samples; | ||
| 159 | shared_memory->dsp_return_data[2] = (end_time - start_time).count(); | ||
| 160 | |||
| 161 | Send(Direction::Host, Message::DecodeInterleavedOK); | ||
| 162 | } break; | ||
| 163 | |||
| 164 | case MapMemory: { | ||
| 165 | [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; | ||
| 166 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 167 | Send(Direction::Host, Message::MapMemoryOK); | ||
| 168 | } break; | ||
| 169 | |||
| 170 | case UnmapMemory: { | ||
| 171 | [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; | ||
| 172 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 173 | Send(Direction::Host, Message::UnmapMemoryOK); | ||
| 174 | } break; | ||
| 175 | |||
| 176 | case GetWorkBufferSizeForMultiStream: { | ||
| 177 | auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]); | ||
| 178 | auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]); | ||
| 179 | |||
| 180 | ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); | ||
| 181 | |||
| 182 | shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize( | ||
| 183 | total_stream_count, stereo_stream_count); | ||
| 184 | Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); | ||
| 185 | } break; | ||
| 186 | |||
| 187 | case InitializeMultiStreamDecodeObject: { | ||
| 188 | auto buffer = shared_memory->host_send_data[0]; | ||
| 189 | auto buffer_size = shared_memory->host_send_data[1]; | ||
| 190 | auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); | ||
| 191 | auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]); | ||
| 192 | auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]); | ||
| 193 | auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]); | ||
| 194 | // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel | ||
| 195 | // mappings, but [6] is never set, and there is not enough room in the argument data for | ||
| 196 | // more than 40 channels, when 255 are possible. | ||
| 197 | // It also means the mapping values are undefined, though likely always 0, | ||
| 198 | // and the mappings given by the game are ignored. The mappings are copied to this | ||
| 199 | // dedicated buffer host side, so let's do as intended. | ||
| 200 | auto mappings = shared_memory->channel_mapping.data(); | ||
| 201 | |||
| 202 | ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); | ||
| 203 | ASSERT(sample_rate >= 0); | ||
| 204 | ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize( | ||
| 205 | total_stream_count, stereo_stream_count)); | ||
| 206 | |||
| 207 | auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); | ||
| 208 | shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder( | ||
| 209 | sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings); | ||
| 210 | |||
| 211 | Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); | ||
| 212 | } break; | ||
| 213 | |||
| 214 | case ShutdownMultiStreamDecodeObject: { | ||
| 215 | auto buffer = shared_memory->host_send_data[0]; | ||
| 216 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 217 | |||
| 218 | auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); | ||
| 219 | shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); | ||
| 220 | |||
| 221 | Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); | ||
| 222 | } break; | ||
| 223 | |||
| 224 | case DecodeInterleavedForMultiStream: { | ||
| 225 | auto start_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 226 | |||
| 227 | auto buffer = shared_memory->host_send_data[0]; | ||
| 228 | auto input_data = shared_memory->host_send_data[1]; | ||
| 229 | auto input_data_size = shared_memory->host_send_data[2]; | ||
| 230 | auto output_data = shared_memory->host_send_data[3]; | ||
| 231 | auto output_data_size = shared_memory->host_send_data[4]; | ||
| 232 | auto final_range = static_cast<u32>(shared_memory->host_send_data[5]); | ||
| 233 | auto reset_requested = shared_memory->host_send_data[6]; | ||
| 234 | |||
| 235 | u32 decoded_samples{0}; | ||
| 236 | |||
| 237 | auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); | ||
| 238 | s32 error_code{OPUS_OK}; | ||
| 239 | if (reset_requested) { | ||
| 240 | error_code = decoder_object.ResetDecoder(); | ||
| 241 | } | ||
| 242 | |||
| 243 | if (error_code == OPUS_OK) { | ||
| 244 | error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, | ||
| 245 | input_data, input_data_size); | ||
| 246 | } | ||
| 247 | |||
| 248 | if (error_code == OPUS_OK) { | ||
| 249 | if (final_range && decoder_object.GetFinalRange() != final_range) { | ||
| 250 | error_code = OPUS_INVALID_PACKET; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | auto end_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 255 | shared_memory->dsp_return_data[0] = error_code; | ||
| 256 | shared_memory->dsp_return_data[1] = decoded_samples; | ||
| 257 | shared_memory->dsp_return_data[2] = (end_time - start_time).count(); | ||
| 258 | |||
| 259 | Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK); | ||
| 260 | } break; | ||
| 261 | |||
| 262 | default: | ||
| 263 | LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg); | ||
| 264 | continue; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h new file mode 100644 index 000000000..fcb89bb40 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <thread> | ||
| 8 | |||
| 9 | #include "audio_core/adsp/apps/opus/shared_memory.h" | ||
| 10 | #include "audio_core/adsp/mailbox.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } // namespace Core | ||
| 16 | |||
| 17 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 18 | |||
| 19 | enum Message : u32 { | ||
| 20 | Invalid = 0, | ||
| 21 | Start = 1, | ||
| 22 | Shutdown = 2, | ||
| 23 | StartOK = 11, | ||
| 24 | ShutdownOK = 12, | ||
| 25 | GetWorkBufferSize = 21, | ||
| 26 | InitializeDecodeObject = 22, | ||
| 27 | ShutdownDecodeObject = 23, | ||
| 28 | DecodeInterleaved = 24, | ||
| 29 | MapMemory = 25, | ||
| 30 | UnmapMemory = 26, | ||
| 31 | GetWorkBufferSizeForMultiStream = 27, | ||
| 32 | InitializeMultiStreamDecodeObject = 28, | ||
| 33 | ShutdownMultiStreamDecodeObject = 29, | ||
| 34 | DecodeInterleavedForMultiStream = 30, | ||
| 35 | |||
| 36 | GetWorkBufferSizeOK = 41, | ||
| 37 | InitializeDecodeObjectOK = 42, | ||
| 38 | ShutdownDecodeObjectOK = 43, | ||
| 39 | DecodeInterleavedOK = 44, | ||
| 40 | MapMemoryOK = 45, | ||
| 41 | UnmapMemoryOK = 46, | ||
| 42 | GetWorkBufferSizeForMultiStreamOK = 47, | ||
| 43 | InitializeMultiStreamDecodeObjectOK = 48, | ||
| 44 | ShutdownMultiStreamDecodeObjectOK = 49, | ||
| 45 | DecodeInterleavedForMultiStreamOK = 50, | ||
| 46 | }; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * The AudioRenderer application running on the ADSP. | ||
| 50 | */ | ||
| 51 | class OpusDecoder { | ||
| 52 | public: | ||
| 53 | explicit OpusDecoder(Core::System& system); | ||
| 54 | ~OpusDecoder(); | ||
| 55 | |||
| 56 | bool IsRunning() const noexcept { | ||
| 57 | return running; | ||
| 58 | } | ||
| 59 | |||
| 60 | void Send(Direction dir, u32 message); | ||
| 61 | u32 Receive(Direction dir, std::stop_token stop_token = {}); | ||
| 62 | |||
| 63 | void SetSharedMemory(SharedMemory& shared_memory_) { | ||
| 64 | shared_memory = &shared_memory_; | ||
| 65 | } | ||
| 66 | |||
| 67 | private: | ||
| 68 | /** | ||
| 69 | * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread. | ||
| 70 | */ | ||
| 71 | void Init(std::stop_token stop_token); | ||
| 72 | /** | ||
| 73 | * Main OpusDecoder thread, responsible for processing the incoming Opus packets. | ||
| 74 | */ | ||
| 75 | void Main(std::stop_token stop_token); | ||
| 76 | |||
| 77 | /// Core system | ||
| 78 | Core::System& system; | ||
| 79 | /// Mailbox to communicate messages with the host, drives the main thread | ||
| 80 | Mailbox mailbox; | ||
| 81 | /// Init thread | ||
| 82 | std::jthread init_thread{}; | ||
| 83 | /// Main thread | ||
| 84 | std::jthread main_thread{}; | ||
| 85 | /// The current state | ||
| 86 | bool running{}; | ||
| 87 | /// Structure shared with the host, input data set by the host before sending a mailbox message, | ||
| 88 | /// and the responses are written back by the OpusDecoder. | ||
| 89 | SharedMemory* shared_memory{}; | ||
| 90 | }; | ||
| 91 | |||
| 92 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp new file mode 100644 index 000000000..f6d362e68 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp | |||
| @@ -0,0 +1,111 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" | ||
| 5 | #include "common/assert.h" | ||
| 6 | |||
| 7 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | bool IsValidChannelCount(u32 channel_count) { | ||
| 11 | return channel_count == 1 || channel_count == 2; | ||
| 12 | } | ||
| 13 | |||
| 14 | bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { | ||
| 15 | return total_stream_count > 0 && stereo_stream_count > 0 && | ||
| 16 | stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count); | ||
| 17 | } | ||
| 18 | } // namespace | ||
| 19 | |||
| 20 | u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count, | ||
| 21 | u32 stereo_stream_count) { | ||
| 22 | if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) { | ||
| 23 | return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) + | ||
| 24 | opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count); | ||
| 25 | } | ||
| 26 | return 0; | ||
| 27 | } | ||
| 28 | |||
| 29 | OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) { | ||
| 30 | auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer); | ||
| 31 | auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2); | ||
| 32 | |||
| 33 | if (new_decoder->magic == DecodeMultiStreamObjectMagic) { | ||
| 34 | if (!new_decoder->initialized || | ||
| 35 | (new_decoder->initialized && new_decoder->self == comparison)) { | ||
| 36 | new_decoder->state_valid = true; | ||
| 37 | } | ||
| 38 | } else { | ||
| 39 | new_decoder->initialized = false; | ||
| 40 | new_decoder->state_valid = true; | ||
| 41 | } | ||
| 42 | return *new_decoder; | ||
| 43 | } | ||
| 44 | |||
| 45 | s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, | ||
| 46 | u32 channel_count, u32 stereo_stream_count, | ||
| 47 | u8* mappings) { | ||
| 48 | if (!state_valid) { | ||
| 49 | return OPUS_INVALID_STATE; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (initialized) { | ||
| 53 | return OPUS_OK; | ||
| 54 | } | ||
| 55 | |||
| 56 | // See OpusDecodeObject::InitializeDecoder for an explanation of this | ||
| 57 | decoder = (LibOpusMSDecoder*)(this + 1); | ||
| 58 | s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count, | ||
| 59 | stereo_stream_count, mappings); | ||
| 60 | if (ret == OPUS_OK) { | ||
| 61 | magic = DecodeMultiStreamObjectMagic; | ||
| 62 | initialized = true; | ||
| 63 | state_valid = true; | ||
| 64 | self = this; | ||
| 65 | final_range = 0; | ||
| 66 | } | ||
| 67 | return ret; | ||
| 68 | } | ||
| 69 | |||
| 70 | s32 OpusMultiStreamDecodeObject::Shutdown() { | ||
| 71 | if (!state_valid) { | ||
| 72 | return OPUS_INVALID_STATE; | ||
| 73 | } | ||
| 74 | |||
| 75 | if (initialized) { | ||
| 76 | magic = 0x0; | ||
| 77 | initialized = false; | ||
| 78 | state_valid = false; | ||
| 79 | self = nullptr; | ||
| 80 | final_range = 0; | ||
| 81 | decoder = nullptr; | ||
| 82 | } | ||
| 83 | return OPUS_OK; | ||
| 84 | } | ||
| 85 | |||
| 86 | s32 OpusMultiStreamDecodeObject::ResetDecoder() { | ||
| 87 | return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE); | ||
| 88 | } | ||
| 89 | |||
| 90 | s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, | ||
| 91 | u64 output_data_size, u64 input_data, u64 input_data_size) { | ||
| 92 | ASSERT(initialized); | ||
| 93 | out_sample_count = 0; | ||
| 94 | |||
| 95 | if (!state_valid) { | ||
| 96 | return OPUS_INVALID_STATE; | ||
| 97 | } | ||
| 98 | |||
| 99 | auto ret_code_or_samples = opus_multistream_decode( | ||
| 100 | decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size), | ||
| 101 | reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0); | ||
| 102 | |||
| 103 | if (ret_code_or_samples < OPUS_OK) { | ||
| 104 | return ret_code_or_samples; | ||
| 105 | } | ||
| 106 | |||
| 107 | out_sample_count = ret_code_or_samples; | ||
| 108 | return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); | ||
| 109 | } | ||
| 110 | |||
| 111 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h new file mode 100644 index 000000000..93558ded5 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <opus_multistream.h> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 11 | using LibOpusMSDecoder = ::OpusMSDecoder; | ||
| 12 | static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF; | ||
| 13 | |||
| 14 | class OpusMultiStreamDecodeObject { | ||
| 15 | public: | ||
| 16 | static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count); | ||
| 17 | static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2); | ||
| 18 | |||
| 19 | s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, | ||
| 20 | u32 stereo_stream_count, u8* mappings); | ||
| 21 | s32 Shutdown(); | ||
| 22 | s32 ResetDecoder(); | ||
| 23 | s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, | ||
| 24 | u64 input_data_size); | ||
| 25 | u32 GetFinalRange() const noexcept { | ||
| 26 | return final_range; | ||
| 27 | } | ||
| 28 | |||
| 29 | private: | ||
| 30 | u32 magic; | ||
| 31 | bool initialized; | ||
| 32 | bool state_valid; | ||
| 33 | OpusMultiStreamDecodeObject* self; | ||
| 34 | u32 final_range; | ||
| 35 | LibOpusMSDecoder* decoder; | ||
| 36 | }; | ||
| 37 | static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>); | ||
| 38 | |||
| 39 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h new file mode 100644 index 000000000..c696731ed --- /dev/null +++ b/src/audio_core/adsp/apps/opus/shared_memory.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 10 | |||
| 11 | struct SharedMemory { | ||
| 12 | std::array<u8, 0x100> channel_mapping{}; | ||
| 13 | std::array<u64, 16> host_send_data{}; | ||
| 14 | std::array<u64, 16> dsp_return_data{}; | ||
| 15 | }; | ||
| 16 | |||
| 17 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h new file mode 100644 index 000000000..1dd40ebfa --- /dev/null +++ b/src/audio_core/adsp/mailbox.h | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "common/bounded_threadsafe_queue.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::ADSP { | ||
| 12 | |||
| 13 | enum class AppMailboxId : u32 { | ||
| 14 | Invalid = 0, | ||
| 15 | AudioRenderer = 50, | ||
| 16 | AudioRendererMemoryMapUnmap = 51, | ||
| 17 | }; | ||
| 18 | |||
| 19 | enum class Direction : u32 { | ||
| 20 | Host, | ||
| 21 | DSP, | ||
| 22 | }; | ||
| 23 | |||
| 24 | class Mailbox { | ||
| 25 | public: | ||
| 26 | void Initialize(AppMailboxId id_) { | ||
| 27 | Reset(); | ||
| 28 | id = id_; | ||
| 29 | } | ||
| 30 | |||
| 31 | AppMailboxId Id() const noexcept { | ||
| 32 | return id; | ||
| 33 | } | ||
| 34 | |||
| 35 | void Send(Direction dir, u32 message) { | ||
| 36 | auto& queue = dir == Direction::Host ? host_queue : adsp_queue; | ||
| 37 | queue.EmplaceWait(message); | ||
| 38 | } | ||
| 39 | |||
| 40 | u32 Receive(Direction dir, std::stop_token stop_token = {}) { | ||
| 41 | auto& queue = dir == Direction::Host ? host_queue : adsp_queue; | ||
| 42 | return queue.PopWait(stop_token); | ||
| 43 | } | ||
| 44 | |||
| 45 | void Reset() { | ||
| 46 | id = AppMailboxId::Invalid; | ||
| 47 | u32 t{}; | ||
| 48 | while (host_queue.TryPop(t)) { | ||
| 49 | } | ||
| 50 | while (adsp_queue.TryPop(t)) { | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | private: | ||
| 55 | AppMailboxId id{0}; | ||
| 56 | Common::SPSCQueue<u32> host_queue; | ||
| 57 | Common::SPSCQueue<u32> adsp_queue; | ||
| 58 | }; | ||
| 59 | |||
| 60 | } // namespace AudioCore::ADSP | ||
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 703ef4494..fcaab2b32 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp | |||
| @@ -11,7 +11,7 @@ namespace AudioCore { | |||
| 11 | AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} { | 11 | AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} { |
| 12 | CreateSinks(); | 12 | CreateSinks(); |
| 13 | // Must be created after the sinks | 13 | // Must be created after the sinks |
| 14 | adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); | 14 | adsp = std::make_unique<ADSP::ADSP>(system, *output_sink); |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | AudioCore ::~AudioCore() { | 17 | AudioCore ::~AudioCore() { |
| @@ -43,7 +43,7 @@ Sink::Sink& AudioCore::GetInputSink() { | |||
| 43 | return *input_sink; | 43 | return *input_sink; |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { | 46 | ADSP::ADSP& AudioCore::ADSP() { |
| 47 | return *adsp; | 47 | return *adsp; |
| 48 | } | 48 | } |
| 49 | 49 | ||
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index ea047773e..e4e27fc66 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h | |||
| @@ -5,8 +5,8 @@ | |||
| 5 | 5 | ||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | 7 | ||
| 8 | #include "audio_core/adsp/adsp.h" | ||
| 8 | #include "audio_core/audio_manager.h" | 9 | #include "audio_core/audio_manager.h" |
| 9 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 10 | #include "audio_core/sink/sink.h" | 10 | #include "audio_core/sink/sink.h" |
| 11 | 11 | ||
| 12 | namespace Core { | 12 | namespace Core { |
| @@ -55,7 +55,7 @@ public: | |||
| 55 | * | 55 | * |
| 56 | * @return Ref to the ADSP. | 56 | * @return Ref to the ADSP. |
| 57 | */ | 57 | */ |
| 58 | AudioRenderer::ADSP::ADSP& GetADSP(); | 58 | ADSP::ADSP& ADSP(); |
| 59 | 59 | ||
| 60 | private: | 60 | private: |
| 61 | /** | 61 | /** |
| @@ -70,7 +70,7 @@ private: | |||
| 70 | /// Sink used for audio input | 70 | /// Sink used for audio input |
| 71 | std::unique_ptr<Sink::Sink> input_sink; | 71 | std::unique_ptr<Sink::Sink> input_sink; |
| 72 | /// The ADSP in the sysmodule | 72 | /// The ADSP in the sysmodule |
| 73 | std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; | 73 | std::unique_ptr<ADSP::ADSP> adsp; |
| 74 | }; | 74 | }; |
| 75 | 75 | ||
| 76 | } // namespace AudioCore | 76 | } // namespace AudioCore |
diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp index d15568e1f..c23ef0990 100644 --- a/src/audio_core/audio_event.cpp +++ b/src/audio_core/audio_event.cpp | |||
| @@ -20,7 +20,6 @@ size_t Event::GetManagerIndex(const Type type) const { | |||
| 20 | default: | 20 | default: |
| 21 | UNREACHABLE(); | 21 | UNREACHABLE(); |
| 22 | } | 22 | } |
| 23 | return 3; | ||
| 24 | } | 23 | } |
| 25 | 24 | ||
| 26 | void Event::SetAudioEvent(const Type type, const bool signalled) { | 25 | void Event::SetAudioEvent(const Type type, const bool signalled) { |
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp index 3dfb613cb..a3667524f 100644 --- a/src/audio_core/audio_in_manager.cpp +++ b/src/audio_core/audio_in_manager.cpp | |||
| @@ -73,7 +73,7 @@ void Manager::BufferReleaseAndRegister() { | |||
| 73 | } | 73 | } |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | 76 | u32 Manager::GetDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names, |
| 77 | [[maybe_unused]] const u32 max_count, | 77 | [[maybe_unused]] const u32 max_count, |
| 78 | [[maybe_unused]] const bool filter) { | 78 | [[maybe_unused]] const bool filter) { |
| 79 | std::scoped_lock l{mutex}; | 79 | std::scoped_lock l{mutex}; |
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h index 8a519df99..5c4614cd1 100644 --- a/src/audio_core/audio_in_manager.h +++ b/src/audio_core/audio_in_manager.h | |||
| @@ -65,8 +65,8 @@ public: | |||
| 65 | * | 65 | * |
| 66 | * @return Number of names written. | 66 | * @return Number of names written. |
| 67 | */ | 67 | */ |
| 68 | u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | 68 | u32 GetDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names, u32 max_count, |
| 69 | u32 max_count, bool filter); | 69 | bool filter); |
| 70 | 70 | ||
| 71 | /// Core system | 71 | /// Core system |
| 72 | Core::System& system; | 72 | Core::System& system; |
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp index f22821360..316ea7c81 100644 --- a/src/audio_core/audio_out_manager.cpp +++ b/src/audio_core/audio_out_manager.cpp | |||
| @@ -73,7 +73,7 @@ void Manager::BufferReleaseAndRegister() { | |||
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | u32 Manager::GetAudioOutDeviceNames( | 75 | u32 Manager::GetAudioOutDeviceNames( |
| 76 | std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { | 76 | std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const { |
| 77 | names.emplace_back("DeviceOut"); | 77 | names.emplace_back("DeviceOut"); |
| 78 | return 1; | 78 | return 1; |
| 79 | } | 79 | } |
diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h index 1e05ec5ed..c3e445d5d 100644 --- a/src/audio_core/audio_out_manager.h +++ b/src/audio_core/audio_out_manager.h | |||
| @@ -61,8 +61,7 @@ public: | |||
| 61 | * @param names - Output container to write names to. | 61 | * @param names - Output container to write names to. |
| 62 | * @return Number of names written. | 62 | * @return Number of names written. |
| 63 | */ | 63 | */ |
| 64 | u32 GetAudioOutDeviceNames( | 64 | u32 GetAudioOutDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const; |
| 65 | std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const; | ||
| 66 | 65 | ||
| 67 | /// Core system | 66 | /// Core system |
| 68 | Core::System& system; | 67 | Core::System& system; |
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp index 320715727..3c53e3afd 100644 --- a/src/audio_core/audio_render_manager.cpp +++ b/src/audio_core/audio_render_manager.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/common/feature_support.h" | 6 | #include "audio_core/common/feature_support.h" |
| 7 | #include "core/core.h" | 7 | #include "core/core.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | Manager::Manager(Core::System& system_) | 11 | Manager::Manager(Core::System& system_) |
| 12 | : system{system_}, system_manager{std::make_unique<SystemManager>(system)} { | 12 | : system{system_}, system_manager{std::make_unique<SystemManager>(system)} { |
| @@ -67,4 +67,4 @@ bool Manager::RemoveSystem(System& system_) { | |||
| 67 | return system_manager->Remove(system_); | 67 | return system_manager->Remove(system_); |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | } // namespace AudioCore::AudioRenderer | 70 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h index fffa5944d..45537b270 100644 --- a/src/audio_core/audio_render_manager.h +++ b/src/audio_core/audio_render_manager.h | |||
| @@ -20,7 +20,7 @@ class System; | |||
| 20 | namespace AudioCore { | 20 | namespace AudioCore { |
| 21 | struct AudioRendererParameterInternal; | 21 | struct AudioRendererParameterInternal; |
| 22 | 22 | ||
| 23 | namespace AudioRenderer { | 23 | namespace Renderer { |
| 24 | /** | 24 | /** |
| 25 | * Wrapper for the audio system manager, handles service calls. | 25 | * Wrapper for the audio system manager, handles service calls. |
| 26 | */ | 26 | */ |
| @@ -101,5 +101,5 @@ private: | |||
| 101 | std::unique_ptr<SystemManager> system_manager{}; | 101 | std::unique_ptr<SystemManager> system_manager{}; |
| 102 | }; | 102 | }; |
| 103 | 103 | ||
| 104 | } // namespace AudioRenderer | 104 | } // namespace Renderer |
| 105 | } // namespace AudioCore | 105 | } // namespace AudioCore |
diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h index 8c7892bcf..6c4e9fdc6 100644 --- a/src/audio_core/common/audio_renderer_parameter.h +++ b/src/audio_core/common/audio_renderer_parameter.h | |||
| @@ -51,10 +51,10 @@ struct AudioRendererSystemContext { | |||
| 51 | s32 session_id; | 51 | s32 session_id; |
| 52 | s8 channels; | 52 | s8 channels; |
| 53 | s16 mix_buffer_count; | 53 | s16 mix_buffer_count; |
| 54 | AudioRenderer::BehaviorInfo* behavior; | 54 | Renderer::BehaviorInfo* behavior; |
| 55 | std::span<s32> depop_buffer; | 55 | std::span<s32> depop_buffer; |
| 56 | AudioRenderer::UpsamplerManager* upsampler_manager; | 56 | Renderer::UpsamplerManager* upsampler_manager; |
| 57 | AudioRenderer::MemoryPoolInfo* memory_pool_info; | 57 | Renderer::MemoryPoolInfo* memory_pool_info; |
| 58 | }; | 58 | }; |
| 59 | 59 | ||
| 60 | } // namespace AudioCore | 60 | } // namespace AudioCore |
diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp new file mode 100644 index 000000000..5b23fce14 --- /dev/null +++ b/src/audio_core/opus/decoder.cpp | |||
| @@ -0,0 +1,179 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/opus/decoder.h" | ||
| 5 | #include "audio_core/opus/hardware_opus.h" | ||
| 6 | #include "audio_core/opus/parameters.h" | ||
| 7 | #include "common/alignment.h" | ||
| 8 | #include "common/swap.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | |||
| 11 | namespace AudioCore::OpusDecoder { | ||
| 12 | using namespace Service::Audio; | ||
| 13 | namespace { | ||
| 14 | OpusPacketHeader ReverseHeader(OpusPacketHeader header) { | ||
| 15 | OpusPacketHeader out; | ||
| 16 | out.size = Common::swap32(header.size); | ||
| 17 | out.final_range = Common::swap32(header.final_range); | ||
| 18 | return out; | ||
| 19 | } | ||
| 20 | } // namespace | ||
| 21 | |||
| 22 | OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_) | ||
| 23 | : system{system_}, hardware_opus{hardware_opus_} {} | ||
| 24 | |||
| 25 | OpusDecoder::~OpusDecoder() { | ||
| 26 | if (decode_object_initialized) { | ||
| 27 | hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, | ||
| 32 | u64 transfer_memory_size) { | ||
| 33 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 34 | shared_buffer_size = transfer_memory_size; | ||
| 35 | shared_buffer = std::make_unique<u8[]>(shared_buffer_size); | ||
| 36 | shared_memory_mapped = true; | ||
| 37 | |||
| 38 | buffer_size = | ||
| 39 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); | ||
| 40 | |||
| 41 | out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; | ||
| 42 | size_t in_data_size{0x600u}; | ||
| 43 | in_data = {out_data.data() - in_data_size, in_data_size}; | ||
| 44 | |||
| 45 | ON_RESULT_FAILURE { | ||
| 46 | if (shared_memory_mapped) { | ||
| 47 | shared_memory_mapped = false; | ||
| 48 | ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | |||
| 52 | R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count, | ||
| 53 | shared_buffer.get(), shared_buffer_size)); | ||
| 54 | |||
| 55 | sample_rate = params.sample_rate; | ||
| 56 | channel_count = params.channel_count; | ||
| 57 | use_large_frame_size = params.use_large_frame_size; | ||
| 58 | decode_object_initialized = true; | ||
| 59 | R_SUCCEED(); | ||
| 60 | } | ||
| 61 | |||
| 62 | Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params, | ||
| 63 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { | ||
| 64 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 65 | shared_buffer_size = transfer_memory_size; | ||
| 66 | shared_buffer = std::make_unique<u8[]>(shared_buffer_size); | ||
| 67 | shared_memory_mapped = true; | ||
| 68 | |||
| 69 | buffer_size = | ||
| 70 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); | ||
| 71 | |||
| 72 | out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; | ||
| 73 | size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)}; | ||
| 74 | in_data = {out_data.data() - in_data_size, in_data_size}; | ||
| 75 | |||
| 76 | ON_RESULT_FAILURE { | ||
| 77 | if (shared_memory_mapped) { | ||
| 78 | shared_memory_mapped = false; | ||
| 79 | ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | |||
| 83 | R_TRY(hardware_opus.InitializeMultiStreamDecodeObject( | ||
| 84 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 85 | params.stereo_stream_count, params.mappings.data(), shared_buffer.get(), | ||
| 86 | shared_buffer_size)); | ||
| 87 | |||
| 88 | sample_rate = params.sample_rate; | ||
| 89 | channel_count = params.channel_count; | ||
| 90 | total_stream_count = params.total_stream_count; | ||
| 91 | stereo_stream_count = params.stereo_stream_count; | ||
| 92 | use_large_frame_size = params.use_large_frame_size; | ||
| 93 | decode_object_initialized = true; | ||
| 94 | R_SUCCEED(); | ||
| 95 | } | ||
| 96 | |||
| 97 | Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken, | ||
| 98 | u32* out_sample_count, std::span<const u8> input_data, | ||
| 99 | std::span<u8> output_data, bool reset) { | ||
| 100 | u32 out_samples; | ||
| 101 | u64 time_taken{}; | ||
| 102 | |||
| 103 | R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall); | ||
| 104 | |||
| 105 | auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())}; | ||
| 106 | OpusPacketHeader header{ReverseHeader(*header_p)}; | ||
| 107 | |||
| 108 | R_UNLESS(in_data.size_bytes() >= header.size && | ||
| 109 | header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(), | ||
| 110 | ResultBufferTooSmall); | ||
| 111 | |||
| 112 | if (!shared_memory_mapped) { | ||
| 113 | R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); | ||
| 114 | shared_memory_mapped = true; | ||
| 115 | } | ||
| 116 | |||
| 117 | std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size); | ||
| 118 | |||
| 119 | R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(), | ||
| 120 | channel_count, in_data.data(), header.size, | ||
| 121 | shared_buffer.get(), time_taken, reset)); | ||
| 122 | |||
| 123 | std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); | ||
| 124 | |||
| 125 | *out_data_size = header.size + sizeof(OpusPacketHeader); | ||
| 126 | *out_sample_count = out_samples; | ||
| 127 | if (out_time_taken) { | ||
| 128 | *out_time_taken = time_taken / 1000; | ||
| 129 | } | ||
| 130 | R_SUCCEED(); | ||
| 131 | } | ||
| 132 | |||
| 133 | Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) { | ||
| 134 | R_SUCCEED_IF(shared_memory_mapped); | ||
| 135 | shared_memory_mapped = true; | ||
| 136 | R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); | ||
| 137 | } | ||
| 138 | |||
| 139 | Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, | ||
| 140 | u32* out_sample_count, | ||
| 141 | std::span<const u8> input_data, | ||
| 142 | std::span<u8> output_data, bool reset) { | ||
| 143 | u32 out_samples; | ||
| 144 | u64 time_taken{}; | ||
| 145 | |||
| 146 | R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall); | ||
| 147 | |||
| 148 | auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())}; | ||
| 149 | OpusPacketHeader header{ReverseHeader(*header_p)}; | ||
| 150 | |||
| 151 | LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}", | ||
| 152 | header.size, input_data.size_bytes(), in_data.size_bytes()); | ||
| 153 | |||
| 154 | R_UNLESS(in_data.size_bytes() >= header.size && | ||
| 155 | header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(), | ||
| 156 | ResultBufferTooSmall); | ||
| 157 | |||
| 158 | if (!shared_memory_mapped) { | ||
| 159 | R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); | ||
| 160 | shared_memory_mapped = true; | ||
| 161 | } | ||
| 162 | |||
| 163 | std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size); | ||
| 164 | |||
| 165 | R_TRY(hardware_opus.DecodeInterleavedForMultiStream( | ||
| 166 | out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(), | ||
| 167 | header.size, shared_buffer.get(), time_taken, reset)); | ||
| 168 | |||
| 169 | std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); | ||
| 170 | |||
| 171 | *out_data_size = header.size + sizeof(OpusPacketHeader); | ||
| 172 | *out_sample_count = out_samples; | ||
| 173 | if (out_time_taken) { | ||
| 174 | *out_time_taken = time_taken / 1000; | ||
| 175 | } | ||
| 176 | R_SUCCEED(); | ||
| 177 | } | ||
| 178 | |||
| 179 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h new file mode 100644 index 000000000..d08d8a4a4 --- /dev/null +++ b/src/audio_core/opus/decoder.h | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/opus/parameters.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "core/hle/kernel/k_transfer_memory.h" | ||
| 11 | #include "core/hle/service/audio/errors.h" | ||
| 12 | |||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace AudioCore::OpusDecoder { | ||
| 18 | class HardwareOpus; | ||
| 19 | |||
| 20 | class OpusDecoder { | ||
| 21 | public: | ||
| 22 | explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_); | ||
| 23 | ~OpusDecoder(); | ||
| 24 | |||
| 25 | Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, | ||
| 26 | u64 transfer_memory_size); | ||
| 27 | Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, | ||
| 28 | u64 transfer_memory_size); | ||
| 29 | Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count, | ||
| 30 | std::span<const u8> input_data, std::span<u8> output_data, bool reset); | ||
| 31 | Result SetContext([[maybe_unused]] std::span<const u8> context); | ||
| 32 | Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, | ||
| 33 | u32* out_sample_count, std::span<const u8> input_data, | ||
| 34 | std::span<u8> output_data, bool reset); | ||
| 35 | |||
| 36 | private: | ||
| 37 | Core::System& system; | ||
| 38 | HardwareOpus& hardware_opus; | ||
| 39 | std::unique_ptr<u8[]> shared_buffer{}; | ||
| 40 | u64 shared_buffer_size; | ||
| 41 | std::span<u8> in_data{}; | ||
| 42 | std::span<u8> out_data{}; | ||
| 43 | u64 buffer_size{}; | ||
| 44 | s32 sample_rate{}; | ||
| 45 | s32 channel_count{}; | ||
| 46 | bool use_large_frame_size{false}; | ||
| 47 | s32 total_stream_count{}; | ||
| 48 | s32 stereo_stream_count{}; | ||
| 49 | bool shared_memory_mapped{false}; | ||
| 50 | bool decode_object_initialized{false}; | ||
| 51 | }; | ||
| 52 | |||
| 53 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/decoder_manager.cpp b/src/audio_core/opus/decoder_manager.cpp new file mode 100644 index 000000000..4a5382973 --- /dev/null +++ b/src/audio_core/opus/decoder_manager.cpp | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/apps/opus/opus_decoder.h" | ||
| 5 | #include "audio_core/opus/decoder_manager.h" | ||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/core.h" | ||
| 8 | |||
| 9 | namespace AudioCore::OpusDecoder { | ||
| 10 | using namespace Service::Audio; | ||
| 11 | |||
| 12 | namespace { | ||
| 13 | bool IsValidChannelCount(u32 channel_count) { | ||
| 14 | return channel_count == 1 || channel_count == 2; | ||
| 15 | } | ||
| 16 | |||
| 17 | bool IsValidMultiStreamChannelCount(u32 channel_count) { | ||
| 18 | return channel_count > 0 && channel_count <= OpusStreamCountMax; | ||
| 19 | } | ||
| 20 | |||
| 21 | bool IsValidSampleRate(u32 sample_rate) { | ||
| 22 | return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 || | ||
| 23 | sample_rate == 24'000 || sample_rate == 48'000; | ||
| 24 | } | ||
| 25 | |||
| 26 | bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) { | ||
| 27 | return total_stream_count > 0 && stereo_stream_count > 0 && | ||
| 28 | stereo_stream_count <= total_stream_count && | ||
| 29 | total_stream_count + stereo_stream_count <= channel_count; | ||
| 30 | } | ||
| 31 | |||
| 32 | } // namespace | ||
| 33 | |||
| 34 | OpusDecoderManager::OpusDecoderManager(Core::System& system_) | ||
| 35 | : system{system_}, hardware_opus{system} { | ||
| 36 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 37 | required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) { | ||
| 42 | OpusParametersEx ex{ | ||
| 43 | .sample_rate = params.sample_rate, | ||
| 44 | .channel_count = params.channel_count, | ||
| 45 | .use_large_frame_size = false, | ||
| 46 | }; | ||
| 47 | R_RETURN(GetWorkBufferSizeExEx(ex, out_size)); | ||
| 48 | } | ||
| 49 | |||
| 50 | Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) { | ||
| 51 | R_RETURN(GetWorkBufferSizeExEx(params, out_size)); | ||
| 52 | } | ||
| 53 | |||
| 54 | Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) { | ||
| 55 | R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount); | ||
| 56 | R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate); | ||
| 57 | |||
| 58 | auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]}; | ||
| 59 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 60 | work_buffer_size += | ||
| 61 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64); | ||
| 62 | out_size = work_buffer_size + 0x600; | ||
| 63 | R_SUCCEED(); | ||
| 64 | } | ||
| 65 | |||
| 66 | Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, | ||
| 67 | u64& out_size) { | ||
| 68 | OpusMultiStreamParametersEx ex{ | ||
| 69 | .sample_rate = params.sample_rate, | ||
| 70 | .channel_count = params.channel_count, | ||
| 71 | .total_stream_count = params.total_stream_count, | ||
| 72 | .stereo_stream_count = params.stereo_stream_count, | ||
| 73 | .use_large_frame_size = false, | ||
| 74 | .mappings = {}, | ||
| 75 | }; | ||
| 76 | R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size)); | ||
| 77 | } | ||
| 78 | |||
| 79 | Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, | ||
| 80 | u64& out_size) { | ||
| 81 | R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size)); | ||
| 82 | } | ||
| 83 | |||
| 84 | Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, | ||
| 85 | u64& out_size) { | ||
| 86 | R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount); | ||
| 87 | R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate); | ||
| 88 | R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count, | ||
| 89 | params.stereo_stream_count), | ||
| 90 | ResultInvalidOpusSampleRate); | ||
| 91 | |||
| 92 | auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream( | ||
| 93 | params.total_stream_count, params.stereo_stream_count)}; | ||
| 94 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 95 | work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64); | ||
| 96 | work_buffer_size += | ||
| 97 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64); | ||
| 98 | out_size = work_buffer_size; | ||
| 99 | R_SUCCEED(); | ||
| 100 | } | ||
| 101 | |||
| 102 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/decoder_manager.h b/src/audio_core/opus/decoder_manager.h new file mode 100644 index 000000000..466e1967b --- /dev/null +++ b/src/audio_core/opus/decoder_manager.h | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/opus/hardware_opus.h" | ||
| 7 | #include "audio_core/opus/parameters.h" | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "core/hle/service/audio/errors.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace AudioCore::OpusDecoder { | ||
| 16 | |||
| 17 | class OpusDecoderManager { | ||
| 18 | public: | ||
| 19 | OpusDecoderManager(Core::System& system); | ||
| 20 | |||
| 21 | HardwareOpus& GetHardwareOpus() { | ||
| 22 | return hardware_opus; | ||
| 23 | } | ||
| 24 | |||
| 25 | Result GetWorkBufferSize(OpusParameters& params, u64& out_size); | ||
| 26 | Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size); | ||
| 27 | Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size); | ||
| 28 | Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size); | ||
| 29 | Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size); | ||
| 30 | Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size); | ||
| 31 | |||
| 32 | private: | ||
| 33 | Core::System& system; | ||
| 34 | HardwareOpus hardware_opus; | ||
| 35 | std::array<u64, MaxChannels> required_workbuffer_sizes{}; | ||
| 36 | }; | ||
| 37 | |||
| 38 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp new file mode 100644 index 000000000..d6544dcb0 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.cpp | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | |||
| 6 | #include "audio_core/audio_core.h" | ||
| 7 | #include "audio_core/opus/hardware_opus.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | |||
| 10 | namespace AudioCore::OpusDecoder { | ||
| 11 | namespace { | ||
| 12 | using namespace Service::Audio; | ||
| 13 | |||
| 14 | static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) { | ||
| 15 | s32 error{static_cast<s32>(error_code)}; | ||
| 16 | ASSERT(error <= OPUS_OK); | ||
| 17 | switch (error) { | ||
| 18 | case OPUS_ALLOC_FAIL: | ||
| 19 | R_THROW(ResultLibOpusAllocFail); | ||
| 20 | case OPUS_INVALID_STATE: | ||
| 21 | R_THROW(ResultLibOpusInvalidState); | ||
| 22 | case OPUS_UNIMPLEMENTED: | ||
| 23 | R_THROW(ResultLibOpusUnimplemented); | ||
| 24 | case OPUS_INVALID_PACKET: | ||
| 25 | R_THROW(ResultLibOpusInvalidPacket); | ||
| 26 | case OPUS_INTERNAL_ERROR: | ||
| 27 | R_THROW(ResultLibOpusInternalError); | ||
| 28 | case OPUS_BUFFER_TOO_SMALL: | ||
| 29 | R_THROW(ResultBufferTooSmall); | ||
| 30 | case OPUS_BAD_ARG: | ||
| 31 | R_THROW(ResultLibOpusBadArg); | ||
| 32 | case OPUS_OK: | ||
| 33 | R_RETURN(ResultSuccess); | ||
| 34 | } | ||
| 35 | UNREACHABLE(); | ||
| 36 | } | ||
| 37 | |||
| 38 | } // namespace | ||
| 39 | |||
| 40 | HardwareOpus::HardwareOpus(Core::System& system_) | ||
| 41 | : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} { | ||
| 42 | opus_decoder.SetSharedMemory(shared_memory); | ||
| 43 | } | ||
| 44 | |||
| 45 | u64 HardwareOpus::GetWorkBufferSize(u32 channel) { | ||
| 46 | if (!opus_decoder.IsRunning()) { | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | std::scoped_lock l{mutex}; | ||
| 50 | shared_memory.host_send_data[0] = channel; | ||
| 51 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize); | ||
| 52 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 53 | if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) { | ||
| 54 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 55 | ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg); | ||
| 56 | return 0; | ||
| 57 | } | ||
| 58 | return shared_memory.dsp_return_data[0]; | ||
| 59 | } | ||
| 60 | |||
| 61 | u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) { | ||
| 62 | std::scoped_lock l{mutex}; | ||
| 63 | shared_memory.host_send_data[0] = total_stream_count; | ||
| 64 | shared_memory.host_send_data[1] = stereo_stream_count; | ||
| 65 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 66 | ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream); | ||
| 67 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 68 | if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) { | ||
| 69 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 70 | ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg); | ||
| 71 | return 0; | ||
| 72 | } | ||
| 73 | return shared_memory.dsp_return_data[0]; | ||
| 74 | } | ||
| 75 | |||
| 76 | Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer, | ||
| 77 | u64 buffer_size) { | ||
| 78 | std::scoped_lock l{mutex}; | ||
| 79 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 80 | shared_memory.host_send_data[1] = buffer_size; | ||
| 81 | shared_memory.host_send_data[2] = sample_rate; | ||
| 82 | shared_memory.host_send_data[3] = channel_count; | ||
| 83 | |||
| 84 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject); | ||
| 85 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 86 | if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) { | ||
| 87 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 88 | ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg); | ||
| 89 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 90 | } | ||
| 91 | |||
| 92 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 93 | } | ||
| 94 | |||
| 95 | Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, | ||
| 96 | u32 total_stream_count, | ||
| 97 | u32 stereo_stream_count, void* mappings, | ||
| 98 | void* buffer, u64 buffer_size) { | ||
| 99 | std::scoped_lock l{mutex}; | ||
| 100 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 101 | shared_memory.host_send_data[1] = buffer_size; | ||
| 102 | shared_memory.host_send_data[2] = sample_rate; | ||
| 103 | shared_memory.host_send_data[3] = channel_count; | ||
| 104 | shared_memory.host_send_data[4] = total_stream_count; | ||
| 105 | shared_memory.host_send_data[5] = stereo_stream_count; | ||
| 106 | |||
| 107 | ASSERT(channel_count <= MaxChannels); | ||
| 108 | std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8)); | ||
| 109 | |||
| 110 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 111 | ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject); | ||
| 112 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 113 | if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) { | ||
| 114 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 115 | ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg); | ||
| 116 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 117 | } | ||
| 118 | |||
| 119 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 120 | } | ||
| 121 | |||
| 122 | Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { | ||
| 123 | std::scoped_lock l{mutex}; | ||
| 124 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 125 | shared_memory.host_send_data[1] = buffer_size; | ||
| 126 | |||
| 127 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject); | ||
| 128 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 129 | ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, | ||
| 130 | "Expected Opus shutdown code {}, got {}", | ||
| 131 | ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg); | ||
| 132 | |||
| 133 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 134 | } | ||
| 135 | |||
| 136 | Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) { | ||
| 137 | std::scoped_lock l{mutex}; | ||
| 138 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 139 | shared_memory.host_send_data[1] = buffer_size; | ||
| 140 | |||
| 141 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 142 | ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject); | ||
| 143 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 144 | ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, | ||
| 145 | "Expected Opus shutdown code {}, got {}", | ||
| 146 | ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg); | ||
| 147 | |||
| 148 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 149 | } | ||
| 150 | |||
| 151 | Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, | ||
| 152 | u64 output_data_size, u32 channel_count, void* input_data, | ||
| 153 | u64 input_data_size, void* buffer, u64& out_time_taken, | ||
| 154 | bool reset) { | ||
| 155 | std::scoped_lock l{mutex}; | ||
| 156 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 157 | shared_memory.host_send_data[1] = (u64)input_data; | ||
| 158 | shared_memory.host_send_data[2] = input_data_size; | ||
| 159 | shared_memory.host_send_data[3] = (u64)output_data; | ||
| 160 | shared_memory.host_send_data[4] = output_data_size; | ||
| 161 | shared_memory.host_send_data[5] = 0; | ||
| 162 | shared_memory.host_send_data[6] = reset; | ||
| 163 | |||
| 164 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved); | ||
| 165 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 166 | if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) { | ||
| 167 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 168 | ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg); | ||
| 169 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 170 | } | ||
| 171 | |||
| 172 | auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])}; | ||
| 173 | if (error_code == OPUS_OK) { | ||
| 174 | out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]); | ||
| 175 | out_time_taken = 1000 * shared_memory.dsp_return_data[2]; | ||
| 176 | } | ||
| 177 | R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); | ||
| 178 | } | ||
| 179 | |||
| 180 | Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, | ||
| 181 | u64 output_data_size, u32 channel_count, | ||
| 182 | void* input_data, u64 input_data_size, | ||
| 183 | void* buffer, u64& out_time_taken, | ||
| 184 | bool reset) { | ||
| 185 | std::scoped_lock l{mutex}; | ||
| 186 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 187 | shared_memory.host_send_data[1] = (u64)input_data; | ||
| 188 | shared_memory.host_send_data[2] = input_data_size; | ||
| 189 | shared_memory.host_send_data[3] = (u64)output_data; | ||
| 190 | shared_memory.host_send_data[4] = output_data_size; | ||
| 191 | shared_memory.host_send_data[5] = 0; | ||
| 192 | shared_memory.host_send_data[6] = reset; | ||
| 193 | |||
| 194 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 195 | ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream); | ||
| 196 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 197 | if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) { | ||
| 198 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 199 | ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg); | ||
| 200 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 201 | } | ||
| 202 | |||
| 203 | auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])}; | ||
| 204 | if (error_code == OPUS_OK) { | ||
| 205 | out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]); | ||
| 206 | out_time_taken = 1000 * shared_memory.dsp_return_data[2]; | ||
| 207 | } | ||
| 208 | R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); | ||
| 209 | } | ||
| 210 | |||
| 211 | Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) { | ||
| 212 | std::scoped_lock l{mutex}; | ||
| 213 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 214 | shared_memory.host_send_data[1] = buffer_size; | ||
| 215 | |||
| 216 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory); | ||
| 217 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 218 | if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) { | ||
| 219 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 220 | ADSP::OpusDecoder::Message::MapMemoryOK, msg); | ||
| 221 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 222 | } | ||
| 223 | R_SUCCEED(); | ||
| 224 | } | ||
| 225 | |||
| 226 | Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) { | ||
| 227 | std::scoped_lock l{mutex}; | ||
| 228 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 229 | shared_memory.host_send_data[1] = buffer_size; | ||
| 230 | |||
| 231 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory); | ||
| 232 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 233 | if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) { | ||
| 234 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 235 | ADSP::OpusDecoder::Message::UnmapMemoryOK, msg); | ||
| 236 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 237 | } | ||
| 238 | R_SUCCEED(); | ||
| 239 | } | ||
| 240 | |||
| 241 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h new file mode 100644 index 000000000..7013a6b40 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.h | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | #include <opus.h> | ||
| 8 | |||
| 9 | #include "audio_core/adsp/apps/opus/opus_decoder.h" | ||
| 10 | #include "audio_core/adsp/apps/opus/shared_memory.h" | ||
| 11 | #include "audio_core/adsp/mailbox.h" | ||
| 12 | #include "core/hle/service/audio/errors.h" | ||
| 13 | |||
| 14 | namespace AudioCore::OpusDecoder { | ||
| 15 | class HardwareOpus { | ||
| 16 | public: | ||
| 17 | HardwareOpus(Core::System& system); | ||
| 18 | |||
| 19 | u64 GetWorkBufferSize(u32 channel); | ||
| 20 | u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count); | ||
| 21 | |||
| 22 | Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer, | ||
| 23 | u64 buffer_size); | ||
| 24 | Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, | ||
| 25 | u32 totaL_stream_count, u32 stereo_stream_count, | ||
| 26 | void* mappings, void* buffer, u64 buffer_size); | ||
| 27 | Result ShutdownDecodeObject(void* buffer, u64 buffer_size); | ||
| 28 | Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size); | ||
| 29 | Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size, | ||
| 30 | u32 channel_count, void* input_data, u64 input_data_size, void* buffer, | ||
| 31 | u64& out_time_taken, bool reset); | ||
| 32 | Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, | ||
| 33 | u64 output_data_size, u32 channel_count, | ||
| 34 | void* input_data, u64 input_data_size, void* buffer, | ||
| 35 | u64& out_time_taken, bool reset); | ||
| 36 | Result MapMemory(void* buffer, u64 buffer_size); | ||
| 37 | Result UnmapMemory(void* buffer, u64 buffer_size); | ||
| 38 | |||
| 39 | private: | ||
| 40 | Core::System& system; | ||
| 41 | std::mutex mutex; | ||
| 42 | ADSP::OpusDecoder::OpusDecoder& opus_decoder; | ||
| 43 | ADSP::OpusDecoder::SharedMemory shared_memory; | ||
| 44 | }; | ||
| 45 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/parameters.h b/src/audio_core/opus/parameters.h new file mode 100644 index 000000000..4c54b2825 --- /dev/null +++ b/src/audio_core/opus/parameters.h | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::OpusDecoder { | ||
| 10 | constexpr size_t OpusStreamCountMax = 255; | ||
| 11 | constexpr size_t MaxChannels = 2; | ||
| 12 | |||
| 13 | struct OpusParameters { | ||
| 14 | /* 0x00 */ u32 sample_rate; | ||
| 15 | /* 0x04 */ u32 channel_count; | ||
| 16 | }; // size = 0x8 | ||
| 17 | static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!"); | ||
| 18 | |||
| 19 | struct OpusParametersEx { | ||
| 20 | /* 0x00 */ u32 sample_rate; | ||
| 21 | /* 0x04 */ u32 channel_count; | ||
| 22 | /* 0x08 */ bool use_large_frame_size; | ||
| 23 | /* 0x09 */ INSERT_PADDING_BYTES(7); | ||
| 24 | }; // size = 0x10 | ||
| 25 | static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!"); | ||
| 26 | |||
| 27 | struct OpusMultiStreamParameters { | ||
| 28 | /* 0x00 */ u32 sample_rate; | ||
| 29 | /* 0x04 */ u32 channel_count; | ||
| 30 | /* 0x08 */ u32 total_stream_count; | ||
| 31 | /* 0x0C */ u32 stereo_stream_count; | ||
| 32 | /* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings; | ||
| 33 | }; // size = 0x110 | ||
| 34 | static_assert(sizeof(OpusMultiStreamParameters) == 0x110, | ||
| 35 | "OpusMultiStreamParameters has the wrong size!"); | ||
| 36 | |||
| 37 | struct OpusMultiStreamParametersEx { | ||
| 38 | /* 0x00 */ u32 sample_rate; | ||
| 39 | /* 0x04 */ u32 channel_count; | ||
| 40 | /* 0x08 */ u32 total_stream_count; | ||
| 41 | /* 0x0C */ u32 stereo_stream_count; | ||
| 42 | /* 0x10 */ bool use_large_frame_size; | ||
| 43 | /* 0x11 */ INSERT_PADDING_BYTES(7); | ||
| 44 | /* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings; | ||
| 45 | }; // size = 0x118 | ||
| 46 | static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, | ||
| 47 | "OpusMultiStreamParametersEx has the wrong size!"); | ||
| 48 | |||
| 49 | struct OpusPacketHeader { | ||
| 50 | /* 0x00 */ u32 size; | ||
| 51 | /* 0x04 */ u32 final_range; | ||
| 52 | }; // size = 0x8 | ||
| 53 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!"); | ||
| 54 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp deleted file mode 100644 index b1db31e93..000000000 --- a/src/audio_core/renderer/adsp/adsp.cpp +++ /dev/null | |||
| @@ -1,117 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 5 | #include "audio_core/renderer/adsp/command_buffer.h" | ||
| 6 | #include "audio_core/sink/sink.h" | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | #include "core/core_timing.h" | ||
| 10 | #include "core/memory.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 13 | |||
| 14 | ADSP::ADSP(Core::System& system_, Sink::Sink& sink_) | ||
| 15 | : system{system_}, memory{system.ApplicationMemory()}, sink{sink_} {} | ||
| 16 | |||
| 17 | ADSP::~ADSP() { | ||
| 18 | ClearCommandBuffers(); | ||
| 19 | } | ||
| 20 | |||
| 21 | State ADSP::GetState() const { | ||
| 22 | if (running) { | ||
| 23 | return State::Started; | ||
| 24 | } | ||
| 25 | return State::Stopped; | ||
| 26 | } | ||
| 27 | |||
| 28 | AudioRenderer_Mailbox* ADSP::GetRenderMailbox() { | ||
| 29 | return &render_mailbox; | ||
| 30 | } | ||
| 31 | |||
| 32 | void ADSP::ClearRemainCount(const u32 session_id) { | ||
| 33 | render_mailbox.ClearRemainCount(session_id); | ||
| 34 | } | ||
| 35 | |||
| 36 | u64 ADSP::GetSignalledTick() const { | ||
| 37 | return render_mailbox.GetSignalledTick(); | ||
| 38 | } | ||
| 39 | |||
| 40 | u64 ADSP::GetTimeTaken() const { | ||
| 41 | return render_mailbox.GetRenderTimeTaken(); | ||
| 42 | } | ||
| 43 | |||
| 44 | u64 ADSP::GetRenderTimeTaken(const u32 session_id) { | ||
| 45 | return render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||
| 46 | } | ||
| 47 | |||
| 48 | u32 ADSP::GetRemainCommandCount(const u32 session_id) const { | ||
| 49 | return render_mailbox.GetRemainCommandCount(session_id); | ||
| 50 | } | ||
| 51 | |||
| 52 | void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) { | ||
| 53 | render_mailbox.SetCommandBuffer(session_id, command_buffer); | ||
| 54 | } | ||
| 55 | |||
| 56 | u64 ADSP::GetRenderingStartTick(const u32 session_id) { | ||
| 57 | return render_mailbox.GetSignalledTick() + | ||
| 58 | render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||
| 59 | } | ||
| 60 | |||
| 61 | bool ADSP::Start() { | ||
| 62 | if (running) { | ||
| 63 | return running; | ||
| 64 | } | ||
| 65 | |||
| 66 | running = true; | ||
| 67 | systems_active++; | ||
| 68 | audio_renderer = std::make_unique<AudioRenderer>(system); | ||
| 69 | audio_renderer->Start(&render_mailbox); | ||
| 70 | render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK); | ||
| 71 | if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { | ||
| 72 | LOG_ERROR( | ||
| 73 | Service_Audio, | ||
| 74 | "Host Audio Renderer -- Failed to receive initialize message response from ADSP!"); | ||
| 75 | } | ||
| 76 | return running; | ||
| 77 | } | ||
| 78 | |||
| 79 | void ADSP::Stop() { | ||
| 80 | systems_active--; | ||
| 81 | if (running && systems_active == 0) { | ||
| 82 | { | ||
| 83 | std::scoped_lock l{mailbox_lock}; | ||
| 84 | render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown); | ||
| 85 | if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) { | ||
| 86 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | ||
| 87 | "message response from ADSP!"); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | audio_renderer->Stop(); | ||
| 91 | running = false; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | void ADSP::Signal() { | ||
| 96 | const auto signalled_tick{system.CoreTiming().GetClockTicks()}; | ||
| 97 | render_mailbox.SetSignalledTick(signalled_tick); | ||
| 98 | render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render); | ||
| 99 | } | ||
| 100 | |||
| 101 | void ADSP::Wait() { | ||
| 102 | std::scoped_lock l{mailbox_lock}; | ||
| 103 | auto response{render_mailbox.HostWaitMessage()}; | ||
| 104 | if (response != RenderMessage::AudioRenderer_RenderResponse) { | ||
| 105 | LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}", | ||
| 106 | static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse), | ||
| 107 | static_cast<u32>(response)); | ||
| 108 | } | ||
| 109 | |||
| 110 | ClearCommandBuffers(); | ||
| 111 | } | ||
| 112 | |||
| 113 | void ADSP::ClearCommandBuffers() { | ||
| 114 | render_mailbox.ClearCommandBuffers(); | ||
| 115 | } | ||
| 116 | |||
| 117 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h deleted file mode 100644 index f7a2f25e4..000000000 --- a/src/audio_core/renderer/adsp/adsp.h +++ /dev/null | |||
| @@ -1,171 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <mutex> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/adsp/audio_renderer.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Core { | ||
| 13 | namespace Memory { | ||
| 14 | class Memory; | ||
| 15 | } | ||
| 16 | class System; | ||
| 17 | } // namespace Core | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | namespace Sink { | ||
| 21 | class Sink; | ||
| 22 | } | ||
| 23 | |||
| 24 | namespace AudioRenderer::ADSP { | ||
| 25 | struct CommandBuffer; | ||
| 26 | |||
| 27 | enum class State { | ||
| 28 | Started, | ||
| 29 | Stopped, | ||
| 30 | }; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Represents the ADSP embedded within the audio sysmodule. | ||
| 34 | * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot. | ||
| 35 | * | ||
| 36 | * The kernel will run apps you program for it, Nintendo have the following: | ||
| 37 | * | ||
| 38 | * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all | ||
| 39 | * audio samples end up, and we skip it entirely, since we have very different backends and | ||
| 40 | * mixing is implicitly handled by the OS (but also due to lack of research/simplicity). | ||
| 41 | * | ||
| 42 | * AudioRenderer - Receives command lists generated by the audio render | ||
| 43 | * system, processes them, and sends the samples to Gmix. | ||
| 44 | * | ||
| 45 | * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix. | ||
| 46 | * Not much research done here, TODO if needed. | ||
| 47 | * | ||
| 48 | * We only implement the AudioRenderer for now. | ||
| 49 | * | ||
| 50 | * Communication for the apps is done through mailboxes, and some shared memory. | ||
| 51 | */ | ||
| 52 | class ADSP { | ||
| 53 | public: | ||
| 54 | explicit ADSP(Core::System& system, Sink::Sink& sink); | ||
| 55 | ~ADSP(); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Start the ADSP. | ||
| 59 | * | ||
| 60 | * @return True if started or already running, otherwise false. | ||
| 61 | */ | ||
| 62 | bool Start(); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Stop the ADSP. | ||
| 66 | */ | ||
| 67 | void Stop(); | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Get the ADSP's state. | ||
| 71 | * | ||
| 72 | * @return Started or Stopped. | ||
| 73 | */ | ||
| 74 | State GetState() const; | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Get the AudioRenderer mailbox to communicate with it. | ||
| 78 | * | ||
| 79 | * @return The AudioRenderer mailbox. | ||
| 80 | */ | ||
| 81 | AudioRenderer_Mailbox* GetRenderMailbox(); | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Get the tick the ADSP was signalled. | ||
| 85 | * | ||
| 86 | * @return The tick the ADSP was signalled. | ||
| 87 | */ | ||
| 88 | u64 GetSignalledTick() const; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Get the total time it took for the ADSP to run the last command lists (both command lists). | ||
| 92 | * | ||
| 93 | * @return The tick the ADSP was signalled. | ||
| 94 | */ | ||
| 95 | u64 GetTimeTaken() const; | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Get the last time a given command list took to run. | ||
| 99 | * | ||
| 100 | * @param session_id - The session id to check (0 or 1). | ||
| 101 | * @return The time it took. | ||
| 102 | */ | ||
| 103 | u64 GetRenderTimeTaken(u32 session_id); | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Clear the remaining command count for a given session. | ||
| 107 | * | ||
| 108 | * @param session_id - The session id to check (0 or 1). | ||
| 109 | */ | ||
| 110 | void ClearRemainCount(u32 session_id); | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Get the remaining number of commands left to process for a command list. | ||
| 114 | * | ||
| 115 | * @param session_id - The session id to check (0 or 1). | ||
| 116 | * @return The number of commands remaining. | ||
| 117 | */ | ||
| 118 | u32 GetRemainCommandCount(u32 session_id) const; | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Get the last tick a command list started processing. | ||
| 122 | * | ||
| 123 | * @param session_id - The session id to check (0 or 1). | ||
| 124 | * @return The last tick the given command list started. | ||
| 125 | */ | ||
| 126 | u64 GetRenderingStartTick(u32 session_id); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Set a command buffer to be processed. | ||
| 130 | * | ||
| 131 | * @param session_id - The session id to check (0 or 1). | ||
| 132 | * @param command_buffer - The command buffer to process. | ||
| 133 | */ | ||
| 134 | void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer); | ||
| 135 | |||
| 136 | /** | ||
| 137 | * Clear the command buffers (does not clear the time taken or the remaining command count) | ||
| 138 | */ | ||
| 139 | void ClearCommandBuffers(); | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Signal the AudioRenderer to begin processing. | ||
| 143 | */ | ||
| 144 | void Signal(); | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Wait for the AudioRenderer to finish processing. | ||
| 148 | */ | ||
| 149 | void Wait(); | ||
| 150 | |||
| 151 | private: | ||
| 152 | /// Core system | ||
| 153 | Core::System& system; | ||
| 154 | /// Core memory | ||
| 155 | Core::Memory::Memory& memory; | ||
| 156 | /// Number of systems active, used to prevent accidental shutdowns | ||
| 157 | u8 systems_active{0}; | ||
| 158 | /// ADSP running state | ||
| 159 | std::atomic<bool> running{false}; | ||
| 160 | /// Output sink used by the ADSP | ||
| 161 | Sink::Sink& sink; | ||
| 162 | /// AudioRenderer app | ||
| 163 | std::unique_ptr<AudioRenderer> audio_renderer{}; | ||
| 164 | /// Communication for the AudioRenderer | ||
| 165 | AudioRenderer_Mailbox render_mailbox{}; | ||
| 166 | /// Mailbox lock ffor the render mailbox | ||
| 167 | std::mutex mailbox_lock; | ||
| 168 | }; | ||
| 169 | |||
| 170 | } // namespace AudioRenderer::ADSP | ||
| 171 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp deleted file mode 100644 index 9ca716b60..000000000 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ /dev/null | |||
| @@ -1,225 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <chrono> | ||
| 6 | |||
| 7 | #include "audio_core/audio_core.h" | ||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/adsp/audio_renderer.h" | ||
| 10 | #include "audio_core/sink/sink.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | #include "common/microprofile.h" | ||
| 13 | #include "common/thread.h" | ||
| 14 | #include "core/core.h" | ||
| 15 | #include "core/core_timing.h" | ||
| 16 | |||
| 17 | MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); | ||
| 18 | |||
| 19 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 20 | |||
| 21 | void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) { | ||
| 22 | adsp_messages.enqueue(message_); | ||
| 23 | adsp_event.Set(); | ||
| 24 | } | ||
| 25 | |||
| 26 | RenderMessage AudioRenderer_Mailbox::HostWaitMessage() { | ||
| 27 | host_event.Wait(); | ||
| 28 | RenderMessage msg{RenderMessage::Invalid}; | ||
| 29 | if (!host_messages.try_dequeue(msg)) { | ||
| 30 | LOG_ERROR(Service_Audio, "Failed to dequeue host message!"); | ||
| 31 | } | ||
| 32 | return msg; | ||
| 33 | } | ||
| 34 | |||
| 35 | void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) { | ||
| 36 | host_messages.enqueue(message_); | ||
| 37 | host_event.Set(); | ||
| 38 | } | ||
| 39 | |||
| 40 | RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() { | ||
| 41 | adsp_event.Wait(); | ||
| 42 | RenderMessage msg{RenderMessage::Invalid}; | ||
| 43 | if (!adsp_messages.try_dequeue(msg)) { | ||
| 44 | LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!"); | ||
| 45 | } | ||
| 46 | return msg; | ||
| 47 | } | ||
| 48 | |||
| 49 | CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) { | ||
| 50 | return command_buffers[session_id]; | ||
| 51 | } | ||
| 52 | |||
| 53 | void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) { | ||
| 54 | command_buffers[session_id] = buffer; | ||
| 55 | } | ||
| 56 | |||
| 57 | u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const { | ||
| 58 | return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken; | ||
| 59 | } | ||
| 60 | |||
| 61 | u64 AudioRenderer_Mailbox::GetSignalledTick() const { | ||
| 62 | return signalled_tick; | ||
| 63 | } | ||
| 64 | |||
| 65 | void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) { | ||
| 66 | signalled_tick = tick; | ||
| 67 | } | ||
| 68 | |||
| 69 | void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) { | ||
| 70 | command_buffers[session_id].remaining_command_count = 0; | ||
| 71 | } | ||
| 72 | |||
| 73 | u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const { | ||
| 74 | return command_buffers[session_id].remaining_command_count; | ||
| 75 | } | ||
| 76 | |||
| 77 | void AudioRenderer_Mailbox::ClearCommandBuffers() { | ||
| 78 | command_buffers[0].buffer = 0; | ||
| 79 | command_buffers[0].size = 0; | ||
| 80 | command_buffers[0].reset_buffers = false; | ||
| 81 | command_buffers[1].buffer = 0; | ||
| 82 | command_buffers[1].size = 0; | ||
| 83 | command_buffers[1].reset_buffers = false; | ||
| 84 | } | ||
| 85 | |||
| 86 | AudioRenderer::AudioRenderer(Core::System& system_) | ||
| 87 | : system{system_}, sink{system.AudioCore().GetOutputSink()} { | ||
| 88 | CreateSinkStreams(); | ||
| 89 | } | ||
| 90 | |||
| 91 | AudioRenderer::~AudioRenderer() { | ||
| 92 | Stop(); | ||
| 93 | for (auto& stream : streams) { | ||
| 94 | if (stream) { | ||
| 95 | sink.CloseStream(stream); | ||
| 96 | } | ||
| 97 | stream = nullptr; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) { | ||
| 102 | if (running) { | ||
| 103 | return; | ||
| 104 | } | ||
| 105 | |||
| 106 | mailbox = mailbox_; | ||
| 107 | thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); }); | ||
| 108 | running = true; | ||
| 109 | } | ||
| 110 | |||
| 111 | void AudioRenderer::Stop() { | ||
| 112 | if (!running) { | ||
| 113 | return; | ||
| 114 | } | ||
| 115 | |||
| 116 | for (auto& stream : streams) { | ||
| 117 | stream->Stop(); | ||
| 118 | } | ||
| 119 | thread.join(); | ||
| 120 | running = false; | ||
| 121 | } | ||
| 122 | |||
| 123 | void AudioRenderer::CreateSinkStreams() { | ||
| 124 | u32 channels{sink.GetDeviceChannels()}; | ||
| 125 | for (u32 i = 0; i < MaxRendererSessions; i++) { | ||
| 126 | std::string name{fmt::format("ADSP_RenderStream-{}", i)}; | ||
| 127 | streams[i] = | ||
| 128 | sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); | ||
| 129 | streams[i]->SetRingSize(4); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | void AudioRenderer::ThreadFunc(std::stop_token stop_token) { | ||
| 134 | static constexpr char name[]{"AudioRenderer"}; | ||
| 135 | MicroProfileOnThreadCreate(name); | ||
| 136 | Common::SetCurrentThreadName(name); | ||
| 137 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | ||
| 138 | if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { | ||
| 139 | LOG_ERROR(Service_Audio, | ||
| 140 | "ADSP Audio Renderer -- Failed to receive initialize message from host!"); | ||
| 141 | return; | ||
| 142 | } | ||
| 143 | |||
| 144 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK); | ||
| 145 | |||
| 146 | // 0.12 seconds (2304000 / 19200000) | ||
| 147 | constexpr u64 max_process_time{2'304'000ULL}; | ||
| 148 | |||
| 149 | while (!stop_token.stop_requested()) { | ||
| 150 | auto message{mailbox->ADSPWaitMessage()}; | ||
| 151 | switch (message) { | ||
| 152 | case RenderMessage::AudioRenderer_Shutdown: | ||
| 153 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown); | ||
| 154 | return; | ||
| 155 | |||
| 156 | case RenderMessage::AudioRenderer_Render: { | ||
| 157 | if (system.IsShuttingDown()) [[unlikely]] { | ||
| 158 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); | ||
| 159 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse); | ||
| 160 | continue; | ||
| 161 | } | ||
| 162 | std::array<bool, MaxRendererSessions> buffers_reset{}; | ||
| 163 | std::array<u64, MaxRendererSessions> render_times_taken{}; | ||
| 164 | const auto start_time{system.CoreTiming().GetClockTicks()}; | ||
| 165 | |||
| 166 | for (u32 index = 0; index < 2; index++) { | ||
| 167 | auto& command_buffer{mailbox->GetCommandBuffer(index)}; | ||
| 168 | auto& command_list_processor{command_list_processors[index]}; | ||
| 169 | |||
| 170 | // Check this buffer is valid, as it may not be used. | ||
| 171 | if (command_buffer.buffer != 0) { | ||
| 172 | // If there are no remaining commands (from the previous list), | ||
| 173 | // this is a new command list, initialize it. | ||
| 174 | if (command_buffer.remaining_command_count == 0) { | ||
| 175 | command_list_processor.Initialize(system, command_buffer.buffer, | ||
| 176 | command_buffer.size, streams[index]); | ||
| 177 | } | ||
| 178 | |||
| 179 | if (command_buffer.reset_buffers && !buffers_reset[index]) { | ||
| 180 | streams[index]->ClearQueue(); | ||
| 181 | buffers_reset[index] = true; | ||
| 182 | } | ||
| 183 | |||
| 184 | u64 max_time{max_process_time}; | ||
| 185 | if (index == 1 && command_buffer.applet_resource_user_id == | ||
| 186 | mailbox->GetCommandBuffer(0).applet_resource_user_id) { | ||
| 187 | max_time = max_process_time - render_times_taken[0]; | ||
| 188 | if (render_times_taken[0] > max_process_time) { | ||
| 189 | max_time = 0; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | max_time = std::min(command_buffer.time_limit, max_time); | ||
| 194 | command_list_processor.SetProcessTimeMax(max_time); | ||
| 195 | |||
| 196 | streams[index]->WaitFreeSpace(stop_token); | ||
| 197 | |||
| 198 | // Process the command list | ||
| 199 | { | ||
| 200 | MICROPROFILE_SCOPE(Audio_Renderer); | ||
| 201 | render_times_taken[index] = | ||
| 202 | command_list_processor.Process(index) - start_time; | ||
| 203 | } | ||
| 204 | |||
| 205 | const auto end_time{system.CoreTiming().GetClockTicks()}; | ||
| 206 | |||
| 207 | command_buffer.remaining_command_count = | ||
| 208 | command_list_processor.GetRemainingCommandCount(); | ||
| 209 | command_buffer.render_time_taken = end_time - start_time; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse); | ||
| 214 | } break; | ||
| 215 | |||
| 216 | default: | ||
| 217 | LOG_WARNING(Service_Audio, | ||
| 218 | "ADSP AudioRenderer received an invalid message, msg={:02X}!", | ||
| 219 | static_cast<u32>(message)); | ||
| 220 | break; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h deleted file mode 100644 index 88e558183..000000000 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ /dev/null | |||
| @@ -1,204 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <thread> | ||
| 9 | |||
| 10 | #include "audio_core/renderer/adsp/command_buffer.h" | ||
| 11 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/polyfill_thread.h" | ||
| 14 | #include "common/reader_writer_queue.h" | ||
| 15 | #include "common/thread.h" | ||
| 16 | |||
| 17 | namespace Core { | ||
| 18 | namespace Timing { | ||
| 19 | struct EventType; | ||
| 20 | } | ||
| 21 | class System; | ||
| 22 | } // namespace Core | ||
| 23 | |||
| 24 | namespace AudioCore { | ||
| 25 | namespace Sink { | ||
| 26 | class Sink; | ||
| 27 | } | ||
| 28 | |||
| 29 | namespace AudioRenderer::ADSP { | ||
| 30 | |||
| 31 | enum class RenderMessage { | ||
| 32 | /* 0x00 */ Invalid, | ||
| 33 | /* 0x01 */ AudioRenderer_MapUnmap_Map, | ||
| 34 | /* 0x02 */ AudioRenderer_MapUnmap_MapResponse, | ||
| 35 | /* 0x03 */ AudioRenderer_MapUnmap_Unmap, | ||
| 36 | /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse, | ||
| 37 | /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache, | ||
| 38 | /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse, | ||
| 39 | /* 0x07 */ AudioRenderer_MapUnmap_Shutdown, | ||
| 40 | /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse, | ||
| 41 | /* 0x16 */ AudioRenderer_InitializeOK = 0x16, | ||
| 42 | /* 0x20 */ AudioRenderer_RenderResponse = 0x20, | ||
| 43 | /* 0x2A */ AudioRenderer_Render = 0x2A, | ||
| 44 | /* 0x34 */ AudioRenderer_Shutdown = 0x34, | ||
| 45 | }; | ||
| 46 | |||
| 47 | /** | ||
| 48 | * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer | ||
| 49 | * running on the ADSP. | ||
| 50 | */ | ||
| 51 | class AudioRenderer_Mailbox { | ||
| 52 | public: | ||
| 53 | /** | ||
| 54 | * Send a message from the host to the AudioRenderer. | ||
| 55 | * | ||
| 56 | * @param message - The message to send to the AudioRenderer. | ||
| 57 | */ | ||
| 58 | void HostSendMessage(RenderMessage message); | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Host wait for a message from the AudioRenderer. | ||
| 62 | * | ||
| 63 | * @return The message returned from the AudioRenderer. | ||
| 64 | */ | ||
| 65 | RenderMessage HostWaitMessage(); | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Send a message from the AudioRenderer to the host. | ||
| 69 | * | ||
| 70 | * @param message - The message to send to the host. | ||
| 71 | */ | ||
| 72 | void ADSPSendMessage(RenderMessage message); | ||
| 73 | |||
| 74 | /** | ||
| 75 | * AudioRenderer wait for a message from the host. | ||
| 76 | * | ||
| 77 | * @return The message returned from the AudioRenderer. | ||
| 78 | */ | ||
| 79 | RenderMessage ADSPWaitMessage(); | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Get the command buffer with the given session id (0 or 1). | ||
| 83 | * | ||
| 84 | * @param session_id - The session id to get (0 or 1). | ||
| 85 | * @return The command buffer. | ||
| 86 | */ | ||
| 87 | CommandBuffer& GetCommandBuffer(u32 session_id); | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Set the command buffer with the given session id (0 or 1). | ||
| 91 | * | ||
| 92 | * @param session_id - The session id to get (0 or 1). | ||
| 93 | * @param buffer - The command buffer to set. | ||
| 94 | */ | ||
| 95 | void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer); | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Get the total render time taken for the last command lists sent. | ||
| 99 | * | ||
| 100 | * @return Total render time taken for the last command lists. | ||
| 101 | */ | ||
| 102 | u64 GetRenderTimeTaken() const; | ||
| 103 | |||
| 104 | /** | ||
| 105 | * Get the tick the AudioRenderer was signalled. | ||
| 106 | * | ||
| 107 | * @return The tick the AudioRenderer was signalled. | ||
| 108 | */ | ||
| 109 | u64 GetSignalledTick() const; | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Set the tick the AudioRenderer was signalled. | ||
| 113 | * | ||
| 114 | * @param tick - The tick the AudioRenderer was signalled. | ||
| 115 | */ | ||
| 116 | void SetSignalledTick(u64 tick); | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Clear the remaining command count. | ||
| 120 | * | ||
| 121 | * @param session_id - Index for which command list to clear (0 or 1). | ||
| 122 | */ | ||
| 123 | void ClearRemainCount(u32 session_id); | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Get the remaining command count for a given command list. | ||
| 127 | * | ||
| 128 | * @param session_id - Index for which command list to clear (0 or 1). | ||
| 129 | * @return The remaining command count. | ||
| 130 | */ | ||
| 131 | u32 GetRemainCommandCount(u32 session_id) const; | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Clear the command buffers (does not clear the time taken or the remaining command count). | ||
| 135 | */ | ||
| 136 | void ClearCommandBuffers(); | ||
| 137 | |||
| 138 | private: | ||
| 139 | /// Host signalling event | ||
| 140 | Common::Event host_event{}; | ||
| 141 | /// AudioRenderer signalling event | ||
| 142 | Common::Event adsp_event{}; | ||
| 143 | /// Host message queue | ||
| 144 | |||
| 145 | Common::ReaderWriterQueue<RenderMessage> host_messages{}; | ||
| 146 | /// AudioRenderer message queue | ||
| 147 | |||
| 148 | Common::ReaderWriterQueue<RenderMessage> adsp_messages{}; | ||
| 149 | /// Command buffers | ||
| 150 | |||
| 151 | std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; | ||
| 152 | /// Tick the AudioRnederer was signalled | ||
| 153 | u64 signalled_tick{}; | ||
| 154 | }; | ||
| 155 | |||
| 156 | /** | ||
| 157 | * The AudioRenderer application running on the ADSP. | ||
| 158 | */ | ||
| 159 | class AudioRenderer { | ||
| 160 | public: | ||
| 161 | explicit AudioRenderer(Core::System& system); | ||
| 162 | ~AudioRenderer(); | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Start the AudioRenderer. | ||
| 166 | * | ||
| 167 | * @param mailbox The mailbox to use for this session. | ||
| 168 | */ | ||
| 169 | void Start(AudioRenderer_Mailbox* mailbox); | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Stop the AudioRenderer. | ||
| 173 | */ | ||
| 174 | void Stop(); | ||
| 175 | |||
| 176 | private: | ||
| 177 | /** | ||
| 178 | * Main AudioRenderer thread, responsible for processing the command lists. | ||
| 179 | */ | ||
| 180 | void ThreadFunc(std::stop_token stop_token); | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Creates the streams which will receive the processed samples. | ||
| 184 | */ | ||
| 185 | void CreateSinkStreams(); | ||
| 186 | |||
| 187 | /// Core system | ||
| 188 | Core::System& system; | ||
| 189 | /// Main thread | ||
| 190 | std::jthread thread{}; | ||
| 191 | /// The current state | ||
| 192 | std::atomic<bool> running{}; | ||
| 193 | /// The active mailbox | ||
| 194 | AudioRenderer_Mailbox* mailbox{}; | ||
| 195 | /// The command lists to process | ||
| 196 | std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; | ||
| 197 | /// The output sink the AudioRenderer will use | ||
| 198 | Sink::Sink& sink; | ||
| 199 | /// The streams which will receive the processed samples | ||
| 200 | std::array<Sink::SinkStream*, MaxRendererSessions> streams; | ||
| 201 | }; | ||
| 202 | |||
| 203 | } // namespace AudioRenderer::ADSP | ||
| 204 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h deleted file mode 100644 index 880b279d8..000000000 --- a/src/audio_core/renderer/adsp/command_buffer.h +++ /dev/null | |||
| @@ -1,21 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/common/common.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 10 | |||
| 11 | struct CommandBuffer { | ||
| 12 | CpuAddr buffer; | ||
| 13 | u64 size; | ||
| 14 | u64 time_limit; | ||
| 15 | u32 remaining_command_count; | ||
| 16 | bool reset_buffers; | ||
| 17 | u64 applet_resource_user_id; | ||
| 18 | u64 render_time_taken; | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index 0d9d8f6ce..2d9bf82bb 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "audio_core/sink/sink.h" | 10 | #include "audio_core/sink/sink.h" |
| 11 | #include "core/core.h" | 11 | #include "core/core.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | 14 | ||
| 15 | constexpr std::array usb_device_names{ | 15 | constexpr std::array usb_device_names{ |
| 16 | AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, | 16 | AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, |
| @@ -71,4 +71,4 @@ f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const { | |||
| 71 | return output_sink.GetDeviceVolume(); | 71 | return output_sink.GetDeviceVolume(); |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | } // namespace AudioCore::AudioRenderer | 74 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h index dd6be70ee..ca4040add 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h | |||
| @@ -16,7 +16,7 @@ namespace Sink { | |||
| 16 | class Sink; | 16 | class Sink; |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | namespace AudioRenderer { | 19 | namespace Renderer { |
| 20 | /** | 20 | /** |
| 21 | * An interface to an output audio device available to the Switch. | 21 | * An interface to an output audio device available to the Switch. |
| 22 | */ | 22 | */ |
| @@ -76,5 +76,5 @@ private: | |||
| 76 | const u32 user_revision; | 76 | const u32 user_revision; |
| 77 | }; | 77 | }; |
| 78 | 78 | ||
| 79 | } // namespace AudioRenderer | 79 | } // namespace Renderer |
| 80 | } // namespace AudioCore | 80 | } // namespace AudioCore |
diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp index a8257eb2e..09efe9be9 100644 --- a/src/audio_core/renderer/audio_renderer.cpp +++ b/src/audio_core/renderer/audio_renderer.cpp | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "core/hle/kernel/k_transfer_memory.h" | 9 | #include "core/hle/kernel/k_transfer_memory.h" |
| 10 | #include "core/hle/service/audio/errors.h" | 10 | #include "core/hle/service/audio/errors.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | 13 | ||
| 14 | Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) | 14 | Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) |
| 15 | : core{system_}, manager{manager_}, system{system_, rendered_event} {} | 15 | : core{system_}, manager{manager_}, system{system_, rendered_event} {} |
| @@ -64,4 +64,4 @@ Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performa | |||
| 64 | return system.Update(input, performance, output); | 64 | return system.Update(input, performance, output); |
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | } // namespace AudioCore::AudioRenderer | 67 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h index 90c6f9727..24650278b 100644 --- a/src/audio_core/renderer/audio_renderer.h +++ b/src/audio_core/renderer/audio_renderer.h | |||
| @@ -19,7 +19,7 @@ class KTransferMemory; | |||
| 19 | namespace AudioCore { | 19 | namespace AudioCore { |
| 20 | struct AudioRendererParameterInternal; | 20 | struct AudioRendererParameterInternal; |
| 21 | 21 | ||
| 22 | namespace AudioRenderer { | 22 | namespace Renderer { |
| 23 | class Manager; | 23 | class Manager; |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| @@ -31,7 +31,7 @@ public: | |||
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | * Initialize the renderer. | 33 | * Initialize the renderer. |
| 34 | * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes | 34 | * Registers the system with the Renderer::Manager, allocates workbuffers and initializes |
| 35 | * everything to a default state. | 35 | * everything to a default state. |
| 36 | * | 36 | * |
| 37 | * @param params - Input parameters to initialize the system with. | 37 | * @param params - Input parameters to initialize the system with. |
| @@ -93,5 +93,5 @@ private: | |||
| 93 | System system; | 93 | System system; |
| 94 | }; | 94 | }; |
| 95 | 95 | ||
| 96 | } // namespace AudioRenderer | 96 | } // namespace Renderer |
| 97 | } // namespace AudioCore | 97 | } // namespace AudioCore |
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index 3d2a91312..058539042 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #include "audio_core/common/feature_support.h" | 4 | #include "audio_core/common/feature_support.h" |
| 5 | #include "audio_core/renderer/behavior/behavior_info.h" | 5 | #include "audio_core/renderer/behavior/behavior_info.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} | 9 | BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} |
| 10 | 10 | ||
| @@ -190,4 +190,4 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { | |||
| 190 | return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); | 190 | return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); |
| 191 | } | 191 | } |
| 192 | 192 | ||
| 193 | } // namespace AudioCore::AudioRenderer | 193 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h index b52340229..a4958857a 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "core/hle/service/audio/errors.h" | 11 | #include "core/hle/service/audio/errors.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | /** | 14 | /** |
| 15 | * Holds host and user revisions, checks whether render features can be enabled, and reports errors. | 15 | * Holds host and user revisions, checks whether render features can be enabled, and reports errors. |
| 16 | */ | 16 | */ |
| @@ -264,7 +264,7 @@ public: | |||
| 264 | /** | 264 | /** |
| 265 | * Check if skipping voice pitch and sample rate conversion is supported. | 265 | * Check if skipping voice pitch and sample rate conversion is supported. |
| 266 | * This speeds up the data source commands by skipping resampling if unwanted. | 266 | * This speeds up the data source commands by skipping resampling if unwanted. |
| 267 | * See AudioCore::AudioRenderer::DecodeFromWaveBuffers | 267 | * See AudioCore::Renderer::DecodeFromWaveBuffers |
| 268 | * | 268 | * |
| 269 | * @return True if supported, otherwise false. | 269 | * @return True if supported, otherwise false. |
| 270 | */ | 270 | */ |
| @@ -273,7 +273,7 @@ public: | |||
| 273 | /** | 273 | /** |
| 274 | * Check if resetting played sample count at loop points is supported. | 274 | * Check if resetting played sample count at loop points is supported. |
| 275 | * This resets the number of samples played in a voice state when a loop point is reached. | 275 | * This resets the number of samples played in a voice state when a loop point is reached. |
| 276 | * See AudioCore::AudioRenderer::DecodeFromWaveBuffers | 276 | * See AudioCore::Renderer::DecodeFromWaveBuffers |
| 277 | * | 277 | * |
| 278 | * @return True if supported, otherwise false. | 278 | * @return True if supported, otherwise false. |
| 279 | */ | 279 | */ |
| @@ -373,4 +373,4 @@ public: | |||
| 373 | u32 error_count{}; | 373 | u32 error_count{}; |
| 374 | }; | 374 | }; |
| 375 | 375 | ||
| 376 | } // namespace AudioCore::AudioRenderer | 376 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index e312eb166..667711e17 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp | |||
| @@ -15,7 +15,7 @@ | |||
| 15 | #include "audio_core/renderer/splitter/splitter_context.h" | 15 | #include "audio_core/renderer/splitter/splitter_context.h" |
| 16 | #include "audio_core/renderer/voice/voice_context.h" | 16 | #include "audio_core/renderer/voice/voice_context.h" |
| 17 | 17 | ||
| 18 | namespace AudioCore::AudioRenderer { | 18 | namespace AudioCore::Renderer { |
| 19 | 19 | ||
| 20 | InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, | 20 | InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, |
| 21 | const u32 process_handle_, BehaviorInfo& behaviour_) | 21 | const u32 process_handle_, BehaviorInfo& behaviour_) |
| @@ -536,4 +536,4 @@ Result InfoUpdater::CheckConsumedSize() { | |||
| 536 | return ResultSuccess; | 536 | return ResultSuccess; |
| 537 | } | 537 | } |
| 538 | 538 | ||
| 539 | } // namespace AudioCore::AudioRenderer | 539 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h index c817d8d8d..fb4b7d25a 100644 --- a/src/audio_core/renderer/behavior/info_updater.h +++ b/src/audio_core/renderer/behavior/info_updater.h | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | #include "core/hle/service/audio/errors.h" | 9 | #include "core/hle/service/audio/errors.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | class BehaviorInfo; | 12 | class BehaviorInfo; |
| 13 | class VoiceContext; | 13 | class VoiceContext; |
| 14 | class MixContext; | 14 | class MixContext; |
| @@ -202,4 +202,4 @@ private: | |||
| 202 | BehaviorInfo& behaviour; | 202 | BehaviorInfo& behaviour; |
| 203 | }; | 203 | }; |
| 204 | 204 | ||
| 205 | } // namespace AudioCore::AudioRenderer | 205 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index 0bd418306..67d43e69a 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp | |||
| @@ -16,7 +16,7 @@ | |||
| 16 | #include "audio_core/renderer/voice/voice_info.h" | 16 | #include "audio_core/renderer/voice/voice_info.h" |
| 17 | #include "audio_core/renderer/voice/voice_state.h" | 17 | #include "audio_core/renderer/voice/voice_state.h" |
| 18 | 18 | ||
| 19 | namespace AudioCore::AudioRenderer { | 19 | namespace AudioCore::Renderer { |
| 20 | 20 | ||
| 21 | template <typename T, CommandId Id> | 21 | template <typename T, CommandId Id> |
| 22 | T& CommandBuffer::GenerateStart(const s32 node_id) { | 22 | T& CommandBuffer::GenerateStart(const s32 node_id) { |
| @@ -713,4 +713,4 @@ void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& | |||
| 713 | GenerateEnd<CompressorCommand>(cmd); | 713 | GenerateEnd<CompressorCommand>(cmd); |
| 714 | } | 714 | } |
| 715 | 715 | ||
| 716 | } // namespace AudioCore::AudioRenderer | 716 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h index 162170846..12e8c2c81 100644 --- a/src/audio_core/renderer/command/command_buffer.h +++ b/src/audio_core/renderer/command/command_buffer.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "audio_core/renderer/performance/performance_manager.h" | 10 | #include "audio_core/renderer/performance/performance_manager.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | struct UpsamplerInfo; | 14 | struct UpsamplerInfo; |
| 15 | struct VoiceState; | 15 | struct VoiceState; |
| 16 | class EffectInfoBase; | 16 | class EffectInfoBase; |
| @@ -465,4 +465,4 @@ private: | |||
| 465 | void GenerateEnd(T& cmd); | 465 | void GenerateEnd(T& cmd); |
| 466 | }; | 466 | }; |
| 467 | 467 | ||
| 468 | } // namespace AudioCore::AudioRenderer | 468 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp index fba84c7bd..ccb186209 100644 --- a/src/audio_core/renderer/command/command_generator.cpp +++ b/src/audio_core/renderer/command/command_generator.cpp | |||
| @@ -21,7 +21,7 @@ | |||
| 21 | #include "audio_core/renderer/voice/voice_context.h" | 21 | #include "audio_core/renderer/voice/voice_context.h" |
| 22 | #include "common/alignment.h" | 22 | #include "common/alignment.h" |
| 23 | 23 | ||
| 24 | namespace AudioCore::AudioRenderer { | 24 | namespace AudioCore::Renderer { |
| 25 | 25 | ||
| 26 | CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, | 26 | CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, |
| 27 | const CommandListHeader& command_list_header_, | 27 | const CommandListHeader& command_list_header_, |
| @@ -793,4 +793,4 @@ void CommandGenerator::GeneratePerformanceCommand( | |||
| 793 | command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); | 793 | command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); |
| 794 | } | 794 | } |
| 795 | 795 | ||
| 796 | } // namespace AudioCore::AudioRenderer | 796 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h index b3cd7b408..38ee2a64e 100644 --- a/src/audio_core/renderer/command/command_generator.h +++ b/src/audio_core/renderer/command/command_generator.h | |||
| @@ -12,7 +12,7 @@ | |||
| 12 | namespace AudioCore { | 12 | namespace AudioCore { |
| 13 | struct AudioRendererSystemContext; | 13 | struct AudioRendererSystemContext; |
| 14 | 14 | ||
| 15 | namespace AudioRenderer { | 15 | namespace Renderer { |
| 16 | class CommandBuffer; | 16 | class CommandBuffer; |
| 17 | struct CommandListHeader; | 17 | struct CommandListHeader; |
| 18 | class VoiceContext; | 18 | class VoiceContext; |
| @@ -345,5 +345,5 @@ private: | |||
| 345 | PerformanceManager* performance_manager; | 345 | PerformanceManager* performance_manager; |
| 346 | }; | 346 | }; |
| 347 | 347 | ||
| 348 | } // namespace AudioRenderer | 348 | } // namespace Renderer |
| 349 | } // namespace AudioCore | 349 | } // namespace AudioCore |
diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h index 988530b1f..de9ee070b 100644 --- a/src/audio_core/renderer/command/command_list_header.h +++ b/src/audio_core/renderer/command/command_list_header.h | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | #include "audio_core/common/common.h" | 8 | #include "audio_core/common/common.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | 12 | ||
| 13 | struct CommandListHeader { | 13 | struct CommandListHeader { |
| 14 | u64 buffer_size; | 14 | u64 buffer_size; |
| @@ -19,4 +19,4 @@ struct CommandListHeader { | |||
| 19 | u32 sample_rate; | 19 | u32 sample_rate; |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| 22 | } // namespace AudioCore::AudioRenderer | 22 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp index 3091f587a..0f7aff1b4 100644 --- a/src/audio_core/renderer/command/command_processing_time_estimator.cpp +++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/command/command_processing_time_estimator.h" | 4 | #include "audio_core/renderer/command/command_processing_time_estimator.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 8 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
| 9 | const PcmInt16DataSourceVersion1Command& command) const { | 9 | const PcmInt16DataSourceVersion1Command& command) const { |
| @@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate( | |||
| 27 | 27 | ||
| 28 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 28 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
| 29 | const AdpcmDataSourceVersion1Command& command) const { | 29 | const AdpcmDataSourceVersion1Command& command) const { |
| 30 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | 30 | return static_cast<u32>(command.pitch * 0.46f * 1.2f); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 33 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
| 34 | const AdpcmDataSourceVersion2Command& command) const { | 34 | const AdpcmDataSourceVersion2Command& command) const { |
| 35 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | 35 | return static_cast<u32>(command.pitch * 0.46f * 1.2f); |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 38 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
| @@ -3617,4 +3617,4 @@ u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& co | |||
| 3617 | } | 3617 | } |
| 3618 | } | 3618 | } |
| 3619 | 3619 | ||
| 3620 | } // namespace AudioCore::AudioRenderer | 3620 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h index 452217196..1c76e4ba4 100644 --- a/src/audio_core/renderer/command/command_processing_time_estimator.h +++ b/src/audio_core/renderer/command/command_processing_time_estimator.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/command/commands.h" | 6 | #include "audio_core/renderer/command/commands.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Estimate the processing time required for all commands. | 11 | * Estimate the processing time required for all commands. |
| 12 | */ | 12 | */ |
| @@ -251,4 +251,4 @@ private: | |||
| 251 | u32 buffer_count{}; | 251 | u32 buffer_count{}; |
| 252 | }; | 252 | }; |
| 253 | 253 | ||
| 254 | } // namespace AudioCore::AudioRenderer | 254 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp index e66ed2990..e7f82d3b3 100644 --- a/src/audio_core/renderer/command/data_source/adpcm.cpp +++ b/src/audio_core/renderer/command/data_source/adpcm.cpp | |||
| @@ -3,23 +3,29 @@ | |||
| 3 | 3 | ||
| 4 | #include <span> | 4 | #include <span> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/data_source/adpcm.h" | 7 | #include "audio_core/renderer/command/data_source/adpcm.h" |
| 8 | #include "audio_core/renderer/command/data_source/decode.h" | 8 | #include "audio_core/renderer/command/data_source/decode.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | 12 | void AdpcmDataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 13 | std::string& string) { | 13 | std::string& string) { |
| 14 | string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " | 14 | string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " |
| 15 | "rate {} target sample rate {} src quality {}\n", | 15 | "rate {} target sample rate {} src quality {}\n", |
| 16 | output_index, sample_rate, processor.target_sample_rate, src_quality); | 16 | output_index, sample_rate, processor.target_sample_rate, src_quality); |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | 19 | void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 20 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | 20 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 21 | processor.sample_count)}; | 21 | processor.sample_count)}; |
| 22 | 22 | ||
| 23 | for (auto& wave_buffer : wave_buffers) { | ||
| 24 | wave_buffer.loop_start_offset = wave_buffer.start_offset; | ||
| 25 | wave_buffer.loop_end_offset = wave_buffer.end_offset; | ||
| 26 | wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; | ||
| 27 | } | ||
| 28 | |||
| 23 | DecodeFromWaveBuffersArgs args{ | 29 | DecodeFromWaveBuffersArgs args{ |
| 24 | .sample_format{SampleFormat::Adpcm}, | 30 | .sample_format{SampleFormat::Adpcm}, |
| 25 | .output{out_buffer}, | 31 | .output{out_buffer}, |
| @@ -41,18 +47,18 @@ void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& p | |||
| 41 | DecodeFromWaveBuffers(*processor.memory, args); | 47 | DecodeFromWaveBuffers(*processor.memory, args); |
| 42 | } | 48 | } |
| 43 | 49 | ||
| 44 | bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | 50 | bool AdpcmDataSourceVersion1Command::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 45 | return true; | 51 | return true; |
| 46 | } | 52 | } |
| 47 | 53 | ||
| 48 | void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | 54 | void AdpcmDataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 49 | std::string& string) { | 55 | std::string& string) { |
| 50 | string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " | 56 | string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " |
| 51 | "rate {} target sample rate {} src quality {}\n", | 57 | "rate {} target sample rate {} src quality {}\n", |
| 52 | output_index, sample_rate, processor.target_sample_rate, src_quality); | 58 | output_index, sample_rate, processor.target_sample_rate, src_quality); |
| 53 | } | 59 | } |
| 54 | 60 | ||
| 55 | void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | 61 | void AdpcmDataSourceVersion2Command::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 56 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | 62 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 57 | processor.sample_count)}; | 63 | processor.sample_count)}; |
| 58 | 64 | ||
| @@ -77,8 +83,8 @@ void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& p | |||
| 77 | DecodeFromWaveBuffers(*processor.memory, args); | 83 | DecodeFromWaveBuffers(*processor.memory, args); |
| 78 | } | 84 | } |
| 79 | 85 | ||
| 80 | bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | 86 | bool AdpcmDataSourceVersion2Command::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 81 | return true; | 87 | return true; |
| 82 | } | 88 | } |
| 83 | 89 | ||
| 84 | } // namespace AudioCore::AudioRenderer | 90 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h index a9cf9cee4..487846f0c 100644 --- a/src/audio_core/renderer/command/data_source/adpcm.h +++ b/src/audio_core/renderer/command/data_source/adpcm.h | |||
| @@ -11,11 +11,12 @@ | |||
| 11 | #include "audio_core/renderer/command/icommand.h" | 11 | #include "audio_core/renderer/command/icommand.h" |
| 12 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::ADSP::AudioRenderer { |
| 15 | namespace ADSP { | ||
| 16 | class CommandListProcessor; | 15 | class CommandListProcessor; |
| 17 | } | 16 | } |
| 18 | 17 | ||
| 18 | namespace AudioCore::Renderer { | ||
| 19 | |||
| 19 | /** | 20 | /** |
| 20 | * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers | 21 | * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers |
| 21 | * into the output_index mix buffer. | 22 | * into the output_index mix buffer. |
| @@ -27,14 +28,14 @@ struct AdpcmDataSourceVersion1Command : ICommand { | |||
| 27 | * @param processor - The CommandListProcessor processing this command. | 28 | * @param processor - The CommandListProcessor processing this command. |
| 28 | * @param string - The string to print into. | 29 | * @param string - The string to print into. |
| 29 | */ | 30 | */ |
| 30 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 31 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * Process this command. | 34 | * Process this command. |
| 34 | * | 35 | * |
| 35 | * @param processor - The CommandListProcessor processing this command. | 36 | * @param processor - The CommandListProcessor processing this command. |
| 36 | */ | 37 | */ |
| 37 | void Process(const ADSP::CommandListProcessor& processor) override; | 38 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 38 | 39 | ||
| 39 | /** | 40 | /** |
| 40 | * Verify this command's data is valid. | 41 | * Verify this command's data is valid. |
| @@ -42,13 +43,13 @@ struct AdpcmDataSourceVersion1Command : ICommand { | |||
| 42 | * @param processor - The CommandListProcessor processing this command. | 43 | * @param processor - The CommandListProcessor processing this command. |
| 43 | * @return True if the command is valid, otherwise false. | 44 | * @return True if the command is valid, otherwise false. |
| 44 | */ | 45 | */ |
| 45 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 46 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 46 | 47 | ||
| 47 | /// Quality used for sample rate conversion | 48 | /// Quality used for sample rate conversion |
| 48 | SrcQuality src_quality; | 49 | SrcQuality src_quality; |
| 49 | /// Mix buffer index for decoded samples | 50 | /// Mix buffer index for decoded samples |
| 50 | s16 output_index; | 51 | s16 output_index; |
| 51 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | 52 | /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags) |
| 52 | u16 flags; | 53 | u16 flags; |
| 53 | /// Wavebuffer sample rate | 54 | /// Wavebuffer sample rate |
| 54 | u32 sample_rate; | 55 | u32 sample_rate; |
| @@ -75,14 +76,14 @@ struct AdpcmDataSourceVersion2Command : ICommand { | |||
| 75 | * @param processor - The CommandListProcessor processing this command. | 76 | * @param processor - The CommandListProcessor processing this command. |
| 76 | * @param string - The string to print into. | 77 | * @param string - The string to print into. |
| 77 | */ | 78 | */ |
| 78 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 79 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 79 | 80 | ||
| 80 | /** | 81 | /** |
| 81 | * Process this command. | 82 | * Process this command. |
| 82 | * | 83 | * |
| 83 | * @param processor - The CommandListProcessor processing this command. | 84 | * @param processor - The CommandListProcessor processing this command. |
| 84 | */ | 85 | */ |
| 85 | void Process(const ADSP::CommandListProcessor& processor) override; | 86 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 86 | 87 | ||
| 87 | /** | 88 | /** |
| 88 | * Verify this command's data is valid. | 89 | * Verify this command's data is valid. |
| @@ -90,13 +91,13 @@ struct AdpcmDataSourceVersion2Command : ICommand { | |||
| 90 | * @param processor - The CommandListProcessor processing this command. | 91 | * @param processor - The CommandListProcessor processing this command. |
| 91 | * @return True if the command is valid, otherwise false. | 92 | * @return True if the command is valid, otherwise false. |
| 92 | */ | 93 | */ |
| 93 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 94 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 94 | 95 | ||
| 95 | /// Quality used for sample rate conversion | 96 | /// Quality used for sample rate conversion |
| 96 | SrcQuality src_quality; | 97 | SrcQuality src_quality; |
| 97 | /// Mix buffer index for decoded samples | 98 | /// Mix buffer index for decoded samples |
| 98 | s16 output_index; | 99 | s16 output_index; |
| 99 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | 100 | /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags) |
| 100 | u16 flags; | 101 | u16 flags; |
| 101 | /// Wavebuffer sample rate | 102 | /// Wavebuffer sample rate |
| 102 | u32 sample_rate; | 103 | u32 sample_rate; |
| @@ -116,4 +117,4 @@ struct AdpcmDataSourceVersion2Command : ICommand { | |||
| 116 | u64 data_size; | 117 | u64 data_size; |
| 117 | }; | 118 | }; |
| 118 | 119 | ||
| 119 | } // namespace AudioCore::AudioRenderer | 120 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp index 257aa866e..911dae3c1 100644 --- a/src/audio_core/renderer/command/data_source/decode.cpp +++ b/src/audio_core/renderer/command/data_source/decode.cpp | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include "common/scratch_buffer.h" | 11 | #include "common/scratch_buffer.h" |
| 12 | #include "core/memory.h" | 12 | #include "core/memory.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::Renderer { |
| 15 | 15 | ||
| 16 | constexpr u32 TempBufferSize = 0x3F00; | 16 | constexpr u32 TempBufferSize = 0x3F00; |
| 17 | constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; | 17 | constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; |
| @@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | |||
| 123 | return 0; | 123 | return 0; |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | auto samples_to_process{ | 126 | auto start_pos{req.start_offset + req.offset}; |
| 127 | std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; | 127 | auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)}; |
| 128 | if (samples_to_process == 0) { | ||
| 129 | return 0; | ||
| 130 | } | ||
| 128 | 131 | ||
| 129 | auto samples_to_read{samples_to_process}; | 132 | auto samples_to_read{samples_to_process}; |
| 130 | auto start_pos{req.start_offset + req.offset}; | ||
| 131 | auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; | 133 | auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; |
| 132 | auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + | 134 | auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + |
| 133 | samples_remaining_in_frame}; | 135 | samples_remaining_in_frame}; |
| @@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | |||
| 225 | * @param args - The wavebuffer data, and information for how to decode it. | 227 | * @param args - The wavebuffer data, and information for how to decode it. |
| 226 | */ | 228 | */ |
| 227 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { | 229 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { |
| 230 | static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, | ||
| 231 | auto& played_samples, auto& consumed) -> void { | ||
| 232 | voice_state.wave_buffer_valid[index] = false; | ||
| 233 | voice_state.loop_count = 0; | ||
| 234 | |||
| 235 | if (wavebuffer.stream_ended) { | ||
| 236 | played_samples = 0; | ||
| 237 | } | ||
| 238 | |||
| 239 | index = (index + 1) % MaxWaveBuffers; | ||
| 240 | consumed++; | ||
| 241 | }; | ||
| 228 | auto& voice_state{*args.voice_state}; | 242 | auto& voice_state{*args.voice_state}; |
| 229 | auto remaining_sample_count{args.sample_count}; | 243 | auto remaining_sample_count{args.sample_count}; |
| 230 | auto fraction{voice_state.fraction}; | 244 | auto fraction{voice_state.fraction}; |
| 231 | 245 | ||
| 232 | const auto sample_rate_ratio{ | 246 | const auto sample_rate_ratio{Common::FixedPoint<49, 15>( |
| 233 | (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * | 247 | (f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)}; |
| 234 | args.pitch}; | ||
| 235 | const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; | 248 | const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; |
| 236 | 249 | ||
| 237 | if (size_required < 0) { | 250 | if (size_required < 0) { |
| @@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf | |||
| 298 | auto end_offset{wavebuffer.end_offset}; | 311 | auto end_offset{wavebuffer.end_offset}; |
| 299 | 312 | ||
| 300 | if (wavebuffer.loop && voice_state.loop_count > 0 && | 313 | if (wavebuffer.loop && voice_state.loop_count > 0 && |
| 301 | wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && | ||
| 302 | wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { | 314 | wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { |
| 303 | start_offset = wavebuffer.loop_start_offset; | 315 | start_offset = wavebuffer.loop_start_offset; |
| 304 | end_offset = wavebuffer.loop_end_offset; | 316 | end_offset = wavebuffer.loop_end_offset; |
| 305 | } | 317 | } |
| 306 | 318 | ||
| 307 | DecodeArg decode_arg{.buffer{wavebuffer.buffer}, | 319 | DecodeArg decode_arg{ |
| 308 | .buffer_size{wavebuffer.buffer_size}, | 320 | .buffer{wavebuffer.buffer}, |
| 309 | .start_offset{start_offset}, | 321 | .buffer_size{wavebuffer.buffer_size}, |
| 310 | .end_offset{end_offset}, | 322 | .start_offset{start_offset}, |
| 311 | .channel_count{args.channel_count}, | 323 | .end_offset{end_offset}, |
| 312 | .coefficients{}, | 324 | .channel_count{args.channel_count}, |
| 313 | .adpcm_context{nullptr}, | 325 | .coefficients{}, |
| 314 | .target_channel{args.channel}, | 326 | .adpcm_context{nullptr}, |
| 315 | .offset{offset}, | 327 | .target_channel{args.channel}, |
| 316 | .samples_to_read{samples_to_read - samples_read}}; | 328 | .offset{offset}, |
| 329 | .samples_to_read{samples_to_read - samples_read}, | ||
| 330 | }; | ||
| 317 | 331 | ||
| 318 | s32 samples_decoded{0}; | 332 | s32 samples_decoded{0}; |
| 319 | 333 | ||
| @@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf | |||
| 350 | temp_buffer_pos += samples_decoded; | 364 | temp_buffer_pos += samples_decoded; |
| 351 | offset += samples_decoded; | 365 | offset += samples_decoded; |
| 352 | 366 | ||
| 353 | if (samples_decoded == 0 || offset >= end_offset - start_offset) { | 367 | if (samples_decoded && offset < end_offset - start_offset) { |
| 354 | offset = 0; | 368 | continue; |
| 355 | if (!wavebuffer.loop) { | 369 | } |
| 356 | voice_state.wave_buffer_valid[wavebuffer_index] = false; | 370 | |
| 357 | voice_state.loop_count = 0; | 371 | offset = 0; |
| 358 | 372 | if (wavebuffer.loop) { | |
| 359 | if (wavebuffer.stream_ended) { | 373 | voice_state.loop_count++; |
| 360 | played_sample_count = 0; | 374 | if (wavebuffer.loop_count >= 0 && |
| 361 | } | 375 | (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { |
| 362 | 376 | EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count, | |
| 363 | wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | 377 | wavebuffers_consumed); |
| 364 | wavebuffers_consumed++; | 378 | } |
| 365 | } else { | 379 | |
| 366 | voice_state.loop_count++; | 380 | if (samples_decoded == 0) { |
| 367 | if (wavebuffer.loop_count > 0 && | 381 | is_buffer_starved = true; |
| 368 | (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { | 382 | break; |
| 369 | voice_state.wave_buffer_valid[wavebuffer_index] = false; | 383 | } |
| 370 | voice_state.loop_count = 0; | 384 | |
| 371 | 385 | if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { | |
| 372 | if (wavebuffer.stream_ended) { | 386 | played_sample_count = 0; |
| 373 | played_sample_count = 0; | ||
| 374 | } | ||
| 375 | |||
| 376 | wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | ||
| 377 | wavebuffers_consumed++; | ||
| 378 | } | ||
| 379 | |||
| 380 | if (samples_decoded == 0) { | ||
| 381 | is_buffer_starved = true; | ||
| 382 | break; | ||
| 383 | } | ||
| 384 | |||
| 385 | if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { | ||
| 386 | played_sample_count = 0; | ||
| 387 | } | ||
| 388 | } | 387 | } |
| 388 | } else { | ||
| 389 | EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count, | ||
| 390 | wavebuffers_consumed); | ||
| 389 | } | 391 | } |
| 390 | } | 392 | } |
| 391 | 393 | ||
| @@ -423,4 +425,4 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf | |||
| 423 | voice_state.fraction = fraction; | 425 | voice_state.fraction = fraction; |
| 424 | } | 426 | } |
| 425 | 427 | ||
| 426 | } // namespace AudioCore::AudioRenderer | 428 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h index 4d63d6fa8..5f52f32f0 100644 --- a/src/audio_core/renderer/command/data_source/decode.h +++ b/src/audio_core/renderer/command/data_source/decode.h | |||
| @@ -15,7 +15,7 @@ namespace Core::Memory { | |||
| 15 | class Memory; | 15 | class Memory; |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | namespace AudioCore::AudioRenderer { | 18 | namespace AudioCore::Renderer { |
| 19 | 19 | ||
| 20 | struct DecodeFromWaveBuffersArgs { | 20 | struct DecodeFromWaveBuffersArgs { |
| 21 | SampleFormat sample_format; | 21 | SampleFormat sample_format; |
| @@ -56,4 +56,4 @@ struct DecodeArg { | |||
| 56 | */ | 56 | */ |
| 57 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); | 57 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); |
| 58 | 58 | ||
| 59 | } // namespace AudioCore::AudioRenderer | 59 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp index be77fab69..d1f685656 100644 --- a/src/audio_core/renderer/command/data_source/pcm_float.cpp +++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp | |||
| @@ -1,13 +1,13 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/data_source/decode.h" | 5 | #include "audio_core/renderer/command/data_source/decode.h" |
| 6 | #include "audio_core/renderer/command/data_source/pcm_float.h" | 6 | #include "audio_core/renderer/command/data_source/pcm_float.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | 10 | void PcmFloatDataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 11 | std::string& string) { | 11 | std::string& string) { |
| 12 | string += | 12 | string += |
| 13 | fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " | 13 | fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " |
| @@ -16,10 +16,17 @@ void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& p | |||
| 16 | processor.target_sample_rate, src_quality); | 16 | processor.target_sample_rate, src_quality); |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | 19 | void PcmFloatDataSourceVersion1Command::Process( |
| 20 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 20 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | 21 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 21 | processor.sample_count); | 22 | processor.sample_count); |
| 22 | 23 | ||
| 24 | for (auto& wave_buffer : wave_buffers) { | ||
| 25 | wave_buffer.loop_start_offset = wave_buffer.start_offset; | ||
| 26 | wave_buffer.loop_end_offset = wave_buffer.end_offset; | ||
| 27 | wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; | ||
| 28 | } | ||
| 29 | |||
| 23 | DecodeFromWaveBuffersArgs args{ | 30 | DecodeFromWaveBuffersArgs args{ |
| 24 | .sample_format{SampleFormat::PcmFloat}, | 31 | .sample_format{SampleFormat::PcmFloat}, |
| 25 | .output{out_buffer}, | 32 | .output{out_buffer}, |
| @@ -41,11 +48,12 @@ void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor | |||
| 41 | DecodeFromWaveBuffers(*processor.memory, args); | 48 | DecodeFromWaveBuffers(*processor.memory, args); |
| 42 | } | 49 | } |
| 43 | 50 | ||
| 44 | bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | 51 | bool PcmFloatDataSourceVersion1Command::Verify( |
| 52 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 45 | return true; | 53 | return true; |
| 46 | } | 54 | } |
| 47 | 55 | ||
| 48 | void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | 56 | void PcmFloatDataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 49 | std::string& string) { | 57 | std::string& string) { |
| 50 | string += | 58 | string += |
| 51 | fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " | 59 | fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " |
| @@ -54,7 +62,8 @@ void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& p | |||
| 54 | processor.target_sample_rate, src_quality); | 62 | processor.target_sample_rate, src_quality); |
| 55 | } | 63 | } |
| 56 | 64 | ||
| 57 | void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | 65 | void PcmFloatDataSourceVersion2Command::Process( |
| 66 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 58 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | 67 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 59 | processor.sample_count); | 68 | processor.sample_count); |
| 60 | 69 | ||
| @@ -79,8 +88,9 @@ void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor | |||
| 79 | DecodeFromWaveBuffers(*processor.memory, args); | 88 | DecodeFromWaveBuffers(*processor.memory, args); |
| 80 | } | 89 | } |
| 81 | 90 | ||
| 82 | bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | 91 | bool PcmFloatDataSourceVersion2Command::Verify( |
| 92 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 83 | return true; | 93 | return true; |
| 84 | } | 94 | } |
| 85 | 95 | ||
| 86 | } // namespace AudioCore::AudioRenderer | 96 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h index e4af77c20..2c9d1877e 100644 --- a/src/audio_core/renderer/command/data_source/pcm_float.h +++ b/src/audio_core/renderer/command/data_source/pcm_float.h | |||
| @@ -9,11 +9,12 @@ | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | 9 | #include "audio_core/renderer/command/icommand.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::ADSP::AudioRenderer { |
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | 13 | class CommandListProcessor; |
| 15 | } | 14 | } |
| 16 | 15 | ||
| 16 | namespace AudioCore::Renderer { | ||
| 17 | |||
| 17 | /** | 18 | /** |
| 18 | * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers | 19 | * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers |
| 19 | * into the output_index mix buffer. | 20 | * into the output_index mix buffer. |
| @@ -25,14 +26,14 @@ struct PcmFloatDataSourceVersion1Command : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,13 +41,13 @@ struct PcmFloatDataSourceVersion1Command : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Quality used for sample rate conversion | 46 | /// Quality used for sample rate conversion |
| 46 | SrcQuality src_quality; | 47 | SrcQuality src_quality; |
| 47 | /// Mix buffer index for decoded samples | 48 | /// Mix buffer index for decoded samples |
| 48 | s16 output_index; | 49 | s16 output_index; |
| 49 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | 50 | /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags) |
| 50 | u16 flags; | 51 | u16 flags; |
| 51 | /// Wavebuffer sample rate | 52 | /// Wavebuffer sample rate |
| 52 | u32 sample_rate; | 53 | u32 sample_rate; |
| @@ -73,14 +74,14 @@ struct PcmFloatDataSourceVersion2Command : ICommand { | |||
| 73 | * @param processor - The CommandListProcessor processing this command. | 74 | * @param processor - The CommandListProcessor processing this command. |
| 74 | * @param string - The string to print into. | 75 | * @param string - The string to print into. |
| 75 | */ | 76 | */ |
| 76 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 77 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 77 | 78 | ||
| 78 | /** | 79 | /** |
| 79 | * Process this command. | 80 | * Process this command. |
| 80 | * | 81 | * |
| 81 | * @param processor - The CommandListProcessor processing this command. | 82 | * @param processor - The CommandListProcessor processing this command. |
| 82 | */ | 83 | */ |
| 83 | void Process(const ADSP::CommandListProcessor& processor) override; | 84 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 84 | 85 | ||
| 85 | /** | 86 | /** |
| 86 | * Verify this command's data is valid. | 87 | * Verify this command's data is valid. |
| @@ -88,13 +89,13 @@ struct PcmFloatDataSourceVersion2Command : ICommand { | |||
| 88 | * @param processor - The CommandListProcessor processing this command. | 89 | * @param processor - The CommandListProcessor processing this command. |
| 89 | * @return True if the command is valid, otherwise false. | 90 | * @return True if the command is valid, otherwise false. |
| 90 | */ | 91 | */ |
| 91 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 92 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 92 | 93 | ||
| 93 | /// Quality used for sample rate conversion | 94 | /// Quality used for sample rate conversion |
| 94 | SrcQuality src_quality; | 95 | SrcQuality src_quality; |
| 95 | /// Mix buffer index for decoded samples | 96 | /// Mix buffer index for decoded samples |
| 96 | s16 output_index; | 97 | s16 output_index; |
| 97 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | 98 | /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags) |
| 98 | u16 flags; | 99 | u16 flags; |
| 99 | /// Wavebuffer sample rate | 100 | /// Wavebuffer sample rate |
| 100 | u32 sample_rate; | 101 | u32 sample_rate; |
| @@ -110,4 +111,4 @@ struct PcmFloatDataSourceVersion2Command : ICommand { | |||
| 110 | CpuAddr voice_state; | 111 | CpuAddr voice_state; |
| 111 | }; | 112 | }; |
| 112 | 113 | ||
| 113 | } // namespace AudioCore::AudioRenderer | 114 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp index 7a27463e4..c89a5aaac 100644 --- a/src/audio_core/renderer/command/data_source/pcm_int16.cpp +++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp | |||
| @@ -3,13 +3,13 @@ | |||
| 3 | 3 | ||
| 4 | #include <span> | 4 | #include <span> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/data_source/decode.h" | 7 | #include "audio_core/renderer/command/data_source/decode.h" |
| 8 | #include "audio_core/renderer/command/data_source/pcm_int16.h" | 8 | #include "audio_core/renderer/command/data_source/pcm_int16.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | 12 | void PcmInt16DataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 13 | std::string& string) { | 13 | std::string& string) { |
| 14 | string += | 14 | string += |
| 15 | fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " | 15 | fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " |
| @@ -18,10 +18,17 @@ void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& p | |||
| 18 | processor.target_sample_rate, src_quality); | 18 | processor.target_sample_rate, src_quality); |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | 21 | void PcmInt16DataSourceVersion1Command::Process( |
| 22 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 22 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | 23 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 23 | processor.sample_count); | 24 | processor.sample_count); |
| 24 | 25 | ||
| 26 | for (auto& wave_buffer : wave_buffers) { | ||
| 27 | wave_buffer.loop_start_offset = wave_buffer.start_offset; | ||
| 28 | wave_buffer.loop_end_offset = wave_buffer.end_offset; | ||
| 29 | wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; | ||
| 30 | } | ||
| 31 | |||
| 25 | DecodeFromWaveBuffersArgs args{ | 32 | DecodeFromWaveBuffersArgs args{ |
| 26 | .sample_format{SampleFormat::PcmInt16}, | 33 | .sample_format{SampleFormat::PcmInt16}, |
| 27 | .output{out_buffer}, | 34 | .output{out_buffer}, |
| @@ -43,11 +50,12 @@ void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor | |||
| 43 | DecodeFromWaveBuffers(*processor.memory, args); | 50 | DecodeFromWaveBuffers(*processor.memory, args); |
| 44 | } | 51 | } |
| 45 | 52 | ||
| 46 | bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | 53 | bool PcmInt16DataSourceVersion1Command::Verify( |
| 54 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 47 | return true; | 55 | return true; |
| 48 | } | 56 | } |
| 49 | 57 | ||
| 50 | void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | 58 | void PcmInt16DataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 51 | std::string& string) { | 59 | std::string& string) { |
| 52 | string += | 60 | string += |
| 53 | fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " | 61 | fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " |
| @@ -56,7 +64,8 @@ void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& p | |||
| 56 | processor.target_sample_rate, src_quality); | 64 | processor.target_sample_rate, src_quality); |
| 57 | } | 65 | } |
| 58 | 66 | ||
| 59 | void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | 67 | void PcmInt16DataSourceVersion2Command::Process( |
| 68 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 60 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | 69 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 61 | processor.sample_count); | 70 | processor.sample_count); |
| 62 | DecodeFromWaveBuffersArgs args{ | 71 | DecodeFromWaveBuffersArgs args{ |
| @@ -80,8 +89,9 @@ void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor | |||
| 80 | DecodeFromWaveBuffers(*processor.memory, args); | 89 | DecodeFromWaveBuffers(*processor.memory, args); |
| 81 | } | 90 | } |
| 82 | 91 | ||
| 83 | bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | 92 | bool PcmInt16DataSourceVersion2Command::Verify( |
| 93 | const AudioRenderer::CommandListProcessor& processor) { | ||
| 84 | return true; | 94 | return true; |
| 85 | } | 95 | } |
| 86 | 96 | ||
| 87 | } // namespace AudioCore::AudioRenderer | 97 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h index 5de1ad60d..2c013f003 100644 --- a/src/audio_core/renderer/command/data_source/pcm_int16.h +++ b/src/audio_core/renderer/command/data_source/pcm_int16.h | |||
| @@ -9,11 +9,12 @@ | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | 9 | #include "audio_core/renderer/command/icommand.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::ADSP::AudioRenderer { |
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | 13 | class CommandListProcessor; |
| 15 | } | 14 | } |
| 16 | 15 | ||
| 16 | namespace AudioCore::Renderer { | ||
| 17 | |||
| 17 | /** | 18 | /** |
| 18 | * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers | 19 | * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers |
| 19 | * into the output_index mix buffer. | 20 | * into the output_index mix buffer. |
| @@ -25,14 +26,14 @@ struct PcmInt16DataSourceVersion1Command : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,13 +41,13 @@ struct PcmInt16DataSourceVersion1Command : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Quality used for sample rate conversion | 46 | /// Quality used for sample rate conversion |
| 46 | SrcQuality src_quality; | 47 | SrcQuality src_quality; |
| 47 | /// Mix buffer index for decoded samples | 48 | /// Mix buffer index for decoded samples |
| 48 | s16 output_index; | 49 | s16 output_index; |
| 49 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | 50 | /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags) |
| 50 | u16 flags; | 51 | u16 flags; |
| 51 | /// Wavebuffer sample rate | 52 | /// Wavebuffer sample rate |
| 52 | u32 sample_rate; | 53 | u32 sample_rate; |
| @@ -72,26 +73,26 @@ struct PcmInt16DataSourceVersion2Command : ICommand { | |||
| 72 | * @param processor - The CommandListProcessor processing this command. | 73 | * @param processor - The CommandListProcessor processing this command. |
| 73 | * @param string - The string to print into. | 74 | * @param string - The string to print into. |
| 74 | */ | 75 | */ |
| 75 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 76 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 76 | 77 | ||
| 77 | /** | 78 | /** |
| 78 | * Process this command. | 79 | * Process this command. |
| 79 | * @param processor - The CommandListProcessor processing this command. | 80 | * @param processor - The CommandListProcessor processing this command. |
| 80 | */ | 81 | */ |
| 81 | void Process(const ADSP::CommandListProcessor& processor) override; | 82 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 82 | 83 | ||
| 83 | /** | 84 | /** |
| 84 | * Verify this command's data is valid. | 85 | * Verify this command's data is valid. |
| 85 | * @param processor - The CommandListProcessor processing this command. | 86 | * @param processor - The CommandListProcessor processing this command. |
| 86 | * @return True if the command is valid, otherwise false. | 87 | * @return True if the command is valid, otherwise false. |
| 87 | */ | 88 | */ |
| 88 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 89 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 89 | 90 | ||
| 90 | /// Quality used for sample rate conversion | 91 | /// Quality used for sample rate conversion |
| 91 | SrcQuality src_quality; | 92 | SrcQuality src_quality; |
| 92 | /// Mix buffer index for decoded samples | 93 | /// Mix buffer index for decoded samples |
| 93 | s16 output_index; | 94 | s16 output_index; |
| 94 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | 95 | /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags) |
| 95 | u16 flags; | 96 | u16 flags; |
| 96 | /// Wavebuffer sample rate | 97 | /// Wavebuffer sample rate |
| 97 | u32 sample_rate; | 98 | u32 sample_rate; |
| @@ -107,4 +108,4 @@ struct PcmInt16DataSourceVersion2Command : ICommand { | |||
| 107 | CpuAddr voice_state; | 108 | CpuAddr voice_state; |
| 108 | }; | 109 | }; |
| 109 | 110 | ||
| 110 | } // namespace AudioCore::AudioRenderer | 111 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp index a3e12b3e7..74d9c229f 100644 --- a/src/audio_core/renderer/command/effect/aux_.cpp +++ b/src/audio_core/renderer/command/effect/aux_.cpp | |||
| @@ -1,13 +1,13 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/effect/aux_.h" | 5 | #include "audio_core/renderer/command/effect/aux_.h" |
| 6 | #include "audio_core/renderer/effect/aux_.h" | 6 | #include "audio_core/renderer/effect/aux_.h" |
| 7 | #include "core/core.h" | 7 | #include "core/core.h" |
| 8 | #include "core/memory.h" | 8 | #include "core/memory.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | /** | 11 | /** |
| 12 | * Reset an AuxBuffer. | 12 | * Reset an AuxBuffer. |
| 13 | * | 13 | * |
| @@ -175,13 +175,13 @@ static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr return_info_, | |||
| 175 | return read_count_; | 175 | return read_count_; |
| 176 | } | 176 | } |
| 177 | 177 | ||
| 178 | void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 178 | void AuxCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 179 | std::string& string) { | 179 | std::string& string) { |
| 180 | string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, | 180 | string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, |
| 181 | input, output); | 181 | input, output); |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { | 184 | void AuxCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 185 | auto input_buffer{ | 185 | auto input_buffer{ |
| 186 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | 186 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; |
| 187 | auto output_buffer{ | 187 | auto output_buffer{ |
| @@ -208,8 +208,8 @@ void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 208 | } | 208 | } |
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { | 211 | bool AuxCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 212 | return true; | 212 | return true; |
| 213 | } | 213 | } |
| 214 | 214 | ||
| 215 | } // namespace AudioCore::AudioRenderer | 215 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h index 825c93732..da1e55261 100644 --- a/src/audio_core/renderer/command/effect/aux_.h +++ b/src/audio_core/renderer/command/effect/aux_.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game | 18 | * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game |
| 18 | * memory, and reading into the output buffer from game memory. | 19 | * memory, and reading into the output buffer from game memory. |
| @@ -24,14 +25,14 @@ struct AuxCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct AuxCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | 44 | ||
| 44 | /// Input mix buffer index | 45 | /// Input mix buffer index |
| 45 | s16 input; | 46 | s16 input; |
| @@ -63,4 +64,4 @@ struct AuxCommand : ICommand { | |||
| 63 | bool effect_enabled; | 64 | bool effect_enabled; |
| 64 | }; | 65 | }; |
| 65 | 66 | ||
| 66 | } // namespace AudioCore::AudioRenderer | 67 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp index dea6423dc..3392e7747 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/effect/biquad_filter.h" | 5 | #include "audio_core/renderer/command/effect/biquad_filter.h" |
| 6 | #include "audio_core/renderer/voice/voice_state.h" | 6 | #include "audio_core/renderer/voice/voice_state.h" |
| 7 | #include "common/bit_cast.h" | 7 | #include "common/bit_cast.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Biquad filter float implementation. | 11 | * Biquad filter float implementation. |
| 12 | * | 12 | * |
| @@ -76,14 +76,14 @@ static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> inp | |||
| 76 | } | 76 | } |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 79 | void BiquadFilterCommand::Dump( |
| 80 | std::string& string) { | 80 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 81 | string += fmt::format( | 81 | string += fmt::format( |
| 82 | "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", | 82 | "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", |
| 83 | input, output, needs_init, use_float_processing); | 83 | input, output, needs_init, use_float_processing); |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { | 86 | void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 87 | auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; | 87 | auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; |
| 88 | if (needs_init) { | 88 | if (needs_init) { |
| 89 | *state_ = {}; | 89 | *state_ = {}; |
| @@ -103,8 +103,8 @@ void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 103 | } | 103 | } |
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { | 106 | bool BiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 107 | return true; | 107 | return true; |
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | } // namespace AudioCore::AudioRenderer | 110 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h index 4c9c42d29..0e903930a 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.h +++ b/src/audio_core/renderer/command/effect/biquad_filter.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/voice/voice_state.h" | 10 | #include "audio_core/renderer/voice/voice_state.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to | 20 | * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to |
| 20 | * the output mix buffer. | 21 | * the output mix buffer. |
| @@ -26,14 +27,14 @@ struct BiquadFilterCommand : ICommand { | |||
| 26 | * @param processor - The CommandListProcessor processing this command. | 27 | * @param processor - The CommandListProcessor processing this command. |
| 27 | * @param string - The string to print into. | 28 | * @param string - The string to print into. |
| 28 | */ | 29 | */ |
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 30 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * Process this command. | 33 | * Process this command. |
| 33 | * | 34 | * |
| 34 | * @param processor - The CommandListProcessor processing this command. | 35 | * @param processor - The CommandListProcessor processing this command. |
| 35 | */ | 36 | */ |
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | 37 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 39 | * Verify this command's data is valid. | 40 | * Verify this command's data is valid. |
| @@ -41,7 +42,7 @@ struct BiquadFilterCommand : ICommand { | |||
| 41 | * @param processor - The CommandListProcessor processing this command. | 42 | * @param processor - The CommandListProcessor processing this command. |
| 42 | * @return True if the command is valid, otherwise false. | 43 | * @return True if the command is valid, otherwise false. |
| 43 | */ | 44 | */ |
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 45 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 45 | 46 | ||
| 46 | /// Input mix buffer index | 47 | /// Input mix buffer index |
| 47 | s16 input; | 48 | s16 input; |
| @@ -71,4 +72,4 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, | |||
| 71 | std::array<s16, 3>& b, std::array<s16, 2>& a, | 72 | std::array<s16, 3>& b, std::array<s16, 2>& a, |
| 72 | VoiceState::BiquadFilterState& state, const u32 sample_count); | 73 | VoiceState::BiquadFilterState& state, const u32 sample_count); |
| 73 | 74 | ||
| 74 | } // namespace AudioCore::AudioRenderer | 75 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp index 042fd286e..f235ce027 100644 --- a/src/audio_core/renderer/command/effect/capture.cpp +++ b/src/audio_core/renderer/command/effect/capture.cpp | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/effect/capture.h" | 5 | #include "audio_core/renderer/command/effect/capture.h" |
| 6 | #include "audio_core/renderer/effect/aux_.h" | 6 | #include "audio_core/renderer/effect/aux_.h" |
| 7 | #include "core/memory.h" | 7 | #include "core/memory.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Reset an AuxBuffer. | 11 | * Reset an AuxBuffer. |
| 12 | * | 12 | * |
| @@ -118,13 +118,13 @@ static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_in | |||
| 118 | return write_count_; | 118 | return write_count_; |
| 119 | } | 119 | } |
| 120 | 120 | ||
| 121 | void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 121 | void CaptureCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 122 | std::string& string) { | 122 | std::string& string) { |
| 123 | string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, | 123 | string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, |
| 124 | input, output); | 124 | input, output); |
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { | 127 | void CaptureCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 128 | if (effect_enabled) { | 128 | if (effect_enabled) { |
| 129 | auto input_buffer{ | 129 | auto input_buffer{ |
| 130 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | 130 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; |
| @@ -135,8 +135,8 @@ void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 135 | } | 135 | } |
| 136 | } | 136 | } |
| 137 | 137 | ||
| 138 | bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { | 138 | bool CaptureCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 139 | return true; | 139 | return true; |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | } // namespace AudioCore::AudioRenderer | 142 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h index 8670acb24..a0016c6f6 100644 --- a/src/audio_core/renderer/command/effect/capture.h +++ b/src/audio_core/renderer/command/effect/capture.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory | 18 | * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory |
| 18 | * address. | 19 | * address. |
| @@ -24,14 +25,14 @@ struct CaptureCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct CaptureCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | 44 | ||
| 44 | /// Input mix buffer index | 45 | /// Input mix buffer index |
| 45 | s16 input; | 46 | s16 input; |
| @@ -59,4 +60,4 @@ struct CaptureCommand : ICommand { | |||
| 59 | bool effect_enabled; | 60 | bool effect_enabled; |
| 60 | }; | 61 | }; |
| 61 | 62 | ||
| 62 | } // namespace AudioCore::AudioRenderer | 63 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp index ee9b68d5b..7ff707f4e 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp | |||
| @@ -5,11 +5,11 @@ | |||
| 5 | #include <span> | 5 | #include <span> |
| 6 | #include <vector> | 6 | #include <vector> |
| 7 | 7 | ||
| 8 | #include "audio_core/renderer/adsp/command_list_processor.h" | 8 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 9 | #include "audio_core/renderer/command/effect/compressor.h" | 9 | #include "audio_core/renderer/command/effect/compressor.h" |
| 10 | #include "audio_core/renderer/effect/compressor.h" | 10 | #include "audio_core/renderer/effect/compressor.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | 13 | ||
| 14 | static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params, | 14 | static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params, |
| 15 | CompressorInfo::State& state) { | 15 | CompressorInfo::State& state) { |
| @@ -110,7 +110,7 @@ static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& param | |||
| 110 | } | 110 | } |
| 111 | } | 111 | } |
| 112 | 112 | ||
| 113 | void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 113 | void CompressorCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 114 | std::string& string) { | 114 | std::string& string) { |
| 115 | string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | 115 | string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); |
| 116 | for (s16 i = 0; i < parameter.channel_count; i++) { | 116 | for (s16 i = 0; i < parameter.channel_count; i++) { |
| @@ -123,7 +123,7 @@ void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& | |||
| 123 | string += "\n"; | 123 | string += "\n"; |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { | 126 | void CompressorCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 127 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; | 127 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; |
| 128 | std::array<std::span<s32>, MaxChannels> output_buffers{}; | 128 | std::array<std::span<s32>, MaxChannels> output_buffers{}; |
| 129 | 129 | ||
| @@ -148,8 +148,8 @@ void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 148 | processor.sample_count); | 148 | processor.sample_count); |
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { | 151 | bool CompressorCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 152 | return true; | 152 | return true; |
| 153 | } | 153 | } |
| 154 | 154 | ||
| 155 | } // namespace AudioCore::AudioRenderer | 155 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h index f8e96cb43..c011aa927 100644 --- a/src/audio_core/renderer/command/effect/compressor.h +++ b/src/audio_core/renderer/command/effect/compressor.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/effect/compressor.h" | 10 | #include "audio_core/renderer/effect/compressor.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for limiting volume between a high and low threshold. | 20 | * AudioRenderer command for limiting volume between a high and low threshold. |
| 20 | * Version 1. | 21 | * Version 1. |
| @@ -26,14 +27,14 @@ struct CompressorCommand : ICommand { | |||
| 26 | * @param processor - The CommandListProcessor processing this command. | 27 | * @param processor - The CommandListProcessor processing this command. |
| 27 | * @param string - The string to print into. | 28 | * @param string - The string to print into. |
| 28 | */ | 29 | */ |
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 30 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * Process this command. | 33 | * Process this command. |
| 33 | * | 34 | * |
| 34 | * @param processor - The CommandListProcessor processing this command. | 35 | * @param processor - The CommandListProcessor processing this command. |
| 35 | */ | 36 | */ |
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | 37 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 39 | * Verify this command's data is valid. | 40 | * Verify this command's data is valid. |
| @@ -41,7 +42,7 @@ struct CompressorCommand : ICommand { | |||
| 41 | * @param processor - The CommandListProcessor processing this command. | 42 | * @param processor - The CommandListProcessor processing this command. |
| 42 | * @return True if the command is valid, otherwise false. | 43 | * @return True if the command is valid, otherwise false. |
| 43 | */ | 44 | */ |
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 45 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 45 | 46 | ||
| 46 | /// Input mix buffer offsets for each channel | 47 | /// Input mix buffer offsets for each channel |
| 47 | std::array<s16, MaxChannels> inputs; | 48 | std::array<s16, MaxChannels> inputs; |
| @@ -57,4 +58,4 @@ struct CompressorCommand : ICommand { | |||
| 57 | bool effect_enabled; | 58 | bool effect_enabled; |
| 58 | }; | 59 | }; |
| 59 | 60 | ||
| 60 | } // namespace AudioCore::AudioRenderer | 61 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp index e536cbb1e..ffb298c07 100644 --- a/src/audio_core/renderer/command/effect/delay.cpp +++ b/src/audio_core/renderer/command/effect/delay.cpp | |||
| @@ -1,10 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/effect/delay.h" | 5 | #include "audio_core/renderer/command/effect/delay.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | /** | 8 | /** |
| 9 | * Update the DelayInfo state according to the given parameters. | 9 | * Update the DelayInfo state according to the given parameters. |
| 10 | * | 10 | * |
| @@ -194,7 +194,7 @@ static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayIn | |||
| 194 | } | 194 | } |
| 195 | } | 195 | } |
| 196 | 196 | ||
| 197 | void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 197 | void DelayCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 198 | std::string& string) { | 198 | std::string& string) { |
| 199 | string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | 199 | string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); |
| 200 | for (u32 i = 0; i < MaxChannels; i++) { | 200 | for (u32 i = 0; i < MaxChannels; i++) { |
| @@ -207,7 +207,7 @@ void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proce | |||
| 207 | string += "\n"; | 207 | string += "\n"; |
| 208 | } | 208 | } |
| 209 | 209 | ||
| 210 | void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { | 210 | void DelayCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 211 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; | 211 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; |
| 212 | std::array<std::span<s32>, MaxChannels> output_buffers{}; | 212 | std::array<std::span<s32>, MaxChannels> output_buffers{}; |
| 213 | 213 | ||
| @@ -231,8 +231,8 @@ void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 231 | processor.sample_count); | 231 | processor.sample_count); |
| 232 | } | 232 | } |
| 233 | 233 | ||
| 234 | bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { | 234 | bool DelayCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 235 | return true; | 235 | return true; |
| 236 | } | 236 | } |
| 237 | 237 | ||
| 238 | } // namespace AudioCore::AudioRenderer | 238 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h index b7a15ae6b..bfeac7af4 100644 --- a/src/audio_core/renderer/command/effect/delay.h +++ b/src/audio_core/renderer/command/effect/delay.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/effect/delay.h" | 10 | #include "audio_core/renderer/effect/delay.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters | 20 | * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters |
| 20 | * and state, outputs receives the delayed samples. | 21 | * and state, outputs receives the delayed samples. |
| @@ -26,14 +27,14 @@ struct DelayCommand : ICommand { | |||
| 26 | * @param processor - The CommandListProcessor processing this command. | 27 | * @param processor - The CommandListProcessor processing this command. |
| 27 | * @param string - The string to print into. | 28 | * @param string - The string to print into. |
| 28 | */ | 29 | */ |
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 30 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * Process this command. | 33 | * Process this command. |
| 33 | * | 34 | * |
| 34 | * @param processor - The CommandListProcessor processing this command. | 35 | * @param processor - The CommandListProcessor processing this command. |
| 35 | */ | 36 | */ |
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | 37 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 39 | * Verify this command's data is valid. | 40 | * Verify this command's data is valid. |
| @@ -41,7 +42,7 @@ struct DelayCommand : ICommand { | |||
| 41 | * @param processor - The CommandListProcessor processing this command. | 42 | * @param processor - The CommandListProcessor processing this command. |
| 42 | * @return True if the command is valid, otherwise false. | 43 | * @return True if the command is valid, otherwise false. |
| 43 | */ | 44 | */ |
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 45 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 45 | 46 | ||
| 46 | /// Input mix buffer offsets for each channel | 47 | /// Input mix buffer offsets for each channel |
| 47 | std::array<s16, MaxChannels> inputs; | 48 | std::array<s16, MaxChannels> inputs; |
| @@ -57,4 +58,4 @@ struct DelayCommand : ICommand { | |||
| 57 | bool effect_enabled; | 58 | bool effect_enabled; |
| 58 | }; | 59 | }; |
| 59 | 60 | ||
| 60 | } // namespace AudioCore::AudioRenderer | 61 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp index d2bfb67cc..ecfdfabc6 100644 --- a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp | |||
| @@ -3,11 +3,11 @@ | |||
| 3 | 3 | ||
| 4 | #include <numbers> | 4 | #include <numbers> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/effect/i3dl2_reverb.h" | 7 | #include "audio_core/renderer/command/effect/i3dl2_reverb.h" |
| 8 | #include "common/polyfill_ranges.h" | 8 | #include "common/polyfill_ranges.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ | 12 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ |
| 13 | 5.0f, | 13 | 5.0f, |
| @@ -394,7 +394,7 @@ static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& par | |||
| 394 | } | 394 | } |
| 395 | } | 395 | } |
| 396 | 396 | ||
| 397 | void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 397 | void I3dl2ReverbCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 398 | std::string& string) { | 398 | std::string& string) { |
| 399 | string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | 399 | string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); |
| 400 | for (u32 i = 0; i < parameter.channel_count; i++) { | 400 | for (u32 i = 0; i < parameter.channel_count; i++) { |
| @@ -407,7 +407,7 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& | |||
| 407 | string += "\n"; | 407 | string += "\n"; |
| 408 | } | 408 | } |
| 409 | 409 | ||
| 410 | void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { | 410 | void I3dl2ReverbCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 411 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; | 411 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; |
| 412 | std::array<std::span<s32>, MaxChannels> output_buffers{}; | 412 | std::array<std::span<s32>, MaxChannels> output_buffers{}; |
| 413 | 413 | ||
| @@ -431,8 +431,8 @@ void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 431 | processor.sample_count); | 431 | processor.sample_count); |
| 432 | } | 432 | } |
| 433 | 433 | ||
| 434 | bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { | 434 | bool I3dl2ReverbCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 435 | return true; | 435 | return true; |
| 436 | } | 436 | } |
| 437 | 437 | ||
| 438 | } // namespace AudioCore::AudioRenderer | 438 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h index 243877056..e4c538ae8 100644 --- a/src/audio_core/renderer/command/effect/i3dl2_reverb.h +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/effect/i3dl2.h" | 10 | #include "audio_core/renderer/effect/i3dl2.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to | 20 | * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to |
| 20 | * the I3DL2 spec, outputs receives the results. | 21 | * the I3DL2 spec, outputs receives the results. |
| @@ -26,14 +27,14 @@ struct I3dl2ReverbCommand : ICommand { | |||
| 26 | * @param processor - The CommandListProcessor processing this command. | 27 | * @param processor - The CommandListProcessor processing this command. |
| 27 | * @param string - The string to print into. | 28 | * @param string - The string to print into. |
| 28 | */ | 29 | */ |
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 30 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * Process this command. | 33 | * Process this command. |
| 33 | * | 34 | * |
| 34 | * @param processor - The CommandListProcessor processing this command. | 35 | * @param processor - The CommandListProcessor processing this command. |
| 35 | */ | 36 | */ |
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | 37 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 39 | * Verify this command's data is valid. | 40 | * Verify this command's data is valid. |
| @@ -41,7 +42,7 @@ struct I3dl2ReverbCommand : ICommand { | |||
| 41 | * @param processor - The CommandListProcessor processing this command. | 42 | * @param processor - The CommandListProcessor processing this command. |
| 42 | * @return True if the command is valid, otherwise false. | 43 | * @return True if the command is valid, otherwise false. |
| 43 | */ | 44 | */ |
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 45 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 45 | 46 | ||
| 46 | /// Input mix buffer offsets for each channel | 47 | /// Input mix buffer offsets for each channel |
| 47 | std::array<s16, MaxChannels> inputs; | 48 | std::array<s16, MaxChannels> inputs; |
| @@ -57,4 +58,4 @@ struct I3dl2ReverbCommand : ICommand { | |||
| 57 | bool effect_enabled; | 58 | bool effect_enabled; |
| 58 | }; | 59 | }; |
| 59 | 60 | ||
| 60 | } // namespace AudioCore::AudioRenderer | 61 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp index 4161a9821..63aa06f5c 100644 --- a/src/audio_core/renderer/command/effect/light_limiter.cpp +++ b/src/audio_core/renderer/command/effect/light_limiter.cpp | |||
| @@ -1,10 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/effect/light_limiter.h" | 5 | #include "audio_core/renderer/command/effect/light_limiter.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | /** | 8 | /** |
| 9 | * Update the LightLimiterInfo state according to the given parameters. | 9 | * Update the LightLimiterInfo state according to the given parameters. |
| 10 | * A no-op. | 10 | * A no-op. |
| @@ -133,8 +133,8 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p | |||
| 133 | } | 133 | } |
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 136 | void LightLimiterVersion1Command::Dump( |
| 137 | std::string& string) { | 137 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 138 | string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); | 138 | string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); |
| 139 | for (u32 i = 0; i < MaxChannels; i++) { | 139 | for (u32 i = 0; i < MaxChannels; i++) { |
| 140 | string += fmt::format("{:02X}, ", inputs[i]); | 140 | string += fmt::format("{:02X}, ", inputs[i]); |
| @@ -146,7 +146,7 @@ void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListP | |||
| 146 | string += "\n"; | 146 | string += "\n"; |
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | 149 | void LightLimiterVersion1Command::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 150 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; | 150 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; |
| 151 | std::array<std::span<s32>, MaxChannels> output_buffers{}; | 151 | std::array<std::span<s32>, MaxChannels> output_buffers{}; |
| 152 | 152 | ||
| @@ -172,12 +172,12 @@ void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& proc | |||
| 172 | processor.sample_count, statistics); | 172 | processor.sample_count, statistics); |
| 173 | } | 173 | } |
| 174 | 174 | ||
| 175 | bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | 175 | bool LightLimiterVersion1Command::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 176 | return true; | 176 | return true; |
| 177 | } | 177 | } |
| 178 | 178 | ||
| 179 | void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 179 | void LightLimiterVersion2Command::Dump( |
| 180 | std::string& string) { | 180 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 181 | string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); | 181 | string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); |
| 182 | for (u32 i = 0; i < MaxChannels; i++) { | 182 | for (u32 i = 0; i < MaxChannels; i++) { |
| 183 | string += fmt::format("{:02X}, ", inputs[i]); | 183 | string += fmt::format("{:02X}, ", inputs[i]); |
| @@ -189,7 +189,7 @@ void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListP | |||
| 189 | string += "\n"; | 189 | string += "\n"; |
| 190 | } | 190 | } |
| 191 | 191 | ||
| 192 | void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | 192 | void LightLimiterVersion2Command::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 193 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; | 193 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; |
| 194 | std::array<std::span<s32>, MaxChannels> output_buffers{}; | 194 | std::array<std::span<s32>, MaxChannels> output_buffers{}; |
| 195 | 195 | ||
| @@ -215,8 +215,8 @@ void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& proc | |||
| 215 | processor.sample_count, statistics); | 215 | processor.sample_count, statistics); |
| 216 | } | 216 | } |
| 217 | 217 | ||
| 218 | bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | 218 | bool LightLimiterVersion2Command::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 219 | return true; | 219 | return true; |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | } // namespace AudioCore::AudioRenderer | 222 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h index 5d98272c7..6e3ee1b53 100644 --- a/src/audio_core/renderer/command/effect/light_limiter.h +++ b/src/audio_core/renderer/command/effect/light_limiter.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/effect/light_limiter.h" | 10 | #include "audio_core/renderer/effect/light_limiter.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for limiting volume between a high and low threshold. | 20 | * AudioRenderer command for limiting volume between a high and low threshold. |
| 20 | * Version 1. | 21 | * Version 1. |
| @@ -26,14 +27,14 @@ struct LightLimiterVersion1Command : ICommand { | |||
| 26 | * @param processor - The CommandListProcessor processing this command. | 27 | * @param processor - The CommandListProcessor processing this command. |
| 27 | * @param string - The string to print into. | 28 | * @param string - The string to print into. |
| 28 | */ | 29 | */ |
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 30 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * Process this command. | 33 | * Process this command. |
| 33 | * | 34 | * |
| 34 | * @param processor - The CommandListProcessor processing this command. | 35 | * @param processor - The CommandListProcessor processing this command. |
| 35 | */ | 36 | */ |
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | 37 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 39 | * Verify this command's data is valid. | 40 | * Verify this command's data is valid. |
| @@ -41,7 +42,7 @@ struct LightLimiterVersion1Command : ICommand { | |||
| 41 | * @param processor - The CommandListProcessor processing this command. | 42 | * @param processor - The CommandListProcessor processing this command. |
| 42 | * @return True if the command is valid, otherwise false. | 43 | * @return True if the command is valid, otherwise false. |
| 43 | */ | 44 | */ |
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 45 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 45 | 46 | ||
| 46 | /// Input mix buffer offsets for each channel | 47 | /// Input mix buffer offsets for each channel |
| 47 | std::array<s16, MaxChannels> inputs; | 48 | std::array<s16, MaxChannels> inputs; |
| @@ -68,21 +69,21 @@ struct LightLimiterVersion2Command : ICommand { | |||
| 68 | * @param processor - The CommandListProcessor processing this command. | 69 | * @param processor - The CommandListProcessor processing this command. |
| 69 | * @param string - The string to print into. | 70 | * @param string - The string to print into. |
| 70 | */ | 71 | */ |
| 71 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 72 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 72 | 73 | ||
| 73 | /** | 74 | /** |
| 74 | * Process this command. | 75 | * Process this command. |
| 75 | * | 76 | * |
| 76 | * @param processor - The CommandListProcessor processing this command. | 77 | * @param processor - The CommandListProcessor processing this command. |
| 77 | */ | 78 | */ |
| 78 | void Process(const ADSP::CommandListProcessor& processor) override; | 79 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 79 | 80 | ||
| 80 | /** | 81 | /** |
| 81 | * Verify this command's data is valid. | 82 | * Verify this command's data is valid. |
| 82 | * | 83 | * |
| 83 | * @param processor - The CommandListProcessor processing this command. | 84 | * @param processor - The CommandListProcessor processing this command. |
| 84 | */ | 85 | */ |
| 85 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 86 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 86 | 87 | ||
| 87 | /// Input mix buffer offsets for each channel | 88 | /// Input mix buffer offsets for each channel |
| 88 | std::array<s16, MaxChannels> inputs; | 89 | std::array<s16, MaxChannels> inputs; |
| @@ -100,4 +101,4 @@ struct LightLimiterVersion2Command : ICommand { | |||
| 100 | bool effect_enabled; | 101 | bool effect_enabled; |
| 101 | }; | 102 | }; |
| 102 | 103 | ||
| 103 | } // namespace AudioCore::AudioRenderer | 104 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp index 48a7cba8a..208bbeaf2 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp | |||
| @@ -1,20 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/effect/biquad_filter.h" | 5 | #include "audio_core/renderer/command/effect/biquad_filter.h" |
| 6 | #include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" | 6 | #include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 10 | void MultiTapBiquadFilterCommand::Dump( |
| 11 | std::string& string) { | 11 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 12 | string += fmt::format( | 12 | string += fmt::format( |
| 13 | "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", | 13 | "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", |
| 14 | input, output, needs_init[0], needs_init[1]); | 14 | input, output, needs_init[0], needs_init[1]); |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { | 17 | void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 18 | if (filter_tap_count > MaxBiquadFilters) { | 18 | if (filter_tap_count > MaxBiquadFilters) { |
| 19 | LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); | 19 | LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); |
| 20 | filter_tap_count = MaxBiquadFilters; | 20 | filter_tap_count = MaxBiquadFilters; |
| @@ -38,8 +38,8 @@ void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& proc | |||
| 38 | } | 38 | } |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { | 41 | bool MultiTapBiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 42 | return true; | 42 | return true; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | } // namespace AudioCore::AudioRenderer | 45 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h index 99c2c0830..50fce80b0 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/voice/voice_info.h" | 10 | #include "audio_core/renderer/voice/voice_info.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for applying multiple biquads at once. | 20 | * AudioRenderer command for applying multiple biquads at once. |
| 20 | */ | 21 | */ |
| @@ -25,14 +26,14 @@ struct MultiTapBiquadFilterCommand : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,7 +41,7 @@ struct MultiTapBiquadFilterCommand : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Input mix buffer index | 46 | /// Input mix buffer index |
| 46 | s16 input; | 47 | s16 input; |
| @@ -56,4 +57,4 @@ struct MultiTapBiquadFilterCommand : ICommand { | |||
| 56 | u8 filter_tap_count; | 57 | u8 filter_tap_count; |
| 57 | }; | 58 | }; |
| 58 | 59 | ||
| 59 | } // namespace AudioCore::AudioRenderer | 60 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp index fc2f15a5e..7f152a962 100644 --- a/src/audio_core/renderer/command/effect/reverb.cpp +++ b/src/audio_core/renderer/command/effect/reverb.cpp | |||
| @@ -4,11 +4,11 @@ | |||
| 4 | #include <numbers> | 4 | #include <numbers> |
| 5 | #include <ranges> | 5 | #include <ranges> |
| 6 | 6 | ||
| 7 | #include "audio_core/renderer/adsp/command_list_processor.h" | 7 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 8 | #include "audio_core/renderer/command/effect/reverb.h" | 8 | #include "audio_core/renderer/command/effect/reverb.h" |
| 9 | #include "common/polyfill_ranges.h" | 9 | #include "common/polyfill_ranges.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | 12 | ||
| 13 | constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { | 13 | constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { |
| 14 | 53.9532470703125f, | 14 | 53.9532470703125f, |
| @@ -396,7 +396,7 @@ static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, Rever | |||
| 396 | } | 396 | } |
| 397 | } | 397 | } |
| 398 | 398 | ||
| 399 | void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 399 | void ReverbCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 400 | std::string& string) { | 400 | std::string& string) { |
| 401 | string += fmt::format( | 401 | string += fmt::format( |
| 402 | "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, | 402 | "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, |
| @@ -411,7 +411,7 @@ void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc | |||
| 411 | string += "\n"; | 411 | string += "\n"; |
| 412 | } | 412 | } |
| 413 | 413 | ||
| 414 | void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { | 414 | void ReverbCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 415 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; | 415 | std::array<std::span<const s32>, MaxChannels> input_buffers{}; |
| 416 | std::array<std::span<s32>, MaxChannels> output_buffers{}; | 416 | std::array<std::span<s32>, MaxChannels> output_buffers{}; |
| 417 | 417 | ||
| @@ -435,8 +435,8 @@ void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 435 | processor.sample_count); | 435 | processor.sample_count); |
| 436 | } | 436 | } |
| 437 | 437 | ||
| 438 | bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { | 438 | bool ReverbCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 439 | return true; | 439 | return true; |
| 440 | } | 440 | } |
| 441 | 441 | ||
| 442 | } // namespace AudioCore::AudioRenderer | 442 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h index 328756150..2056c73f2 100644 --- a/src/audio_core/renderer/command/effect/reverb.h +++ b/src/audio_core/renderer/command/effect/reverb.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/effect/reverb.h" | 10 | #include "audio_core/renderer/effect/reverb.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives | 20 | * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives |
| 20 | * the results. | 21 | * the results. |
| @@ -26,14 +27,14 @@ struct ReverbCommand : ICommand { | |||
| 26 | * @param processor - The CommandListProcessor processing this command. | 27 | * @param processor - The CommandListProcessor processing this command. |
| 27 | * @param string - The string to print into. | 28 | * @param string - The string to print into. |
| 28 | */ | 29 | */ |
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 30 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * Process this command. | 33 | * Process this command. |
| 33 | * | 34 | * |
| 34 | * @param processor - The CommandListProcessor processing this command. | 35 | * @param processor - The CommandListProcessor processing this command. |
| 35 | */ | 36 | */ |
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | 37 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 39 | * Verify this command's data is valid. | 40 | * Verify this command's data is valid. |
| @@ -41,7 +42,7 @@ struct ReverbCommand : ICommand { | |||
| 41 | * @param processor - The CommandListProcessor processing this command. | 42 | * @param processor - The CommandListProcessor processing this command. |
| 42 | * @return True if the command is valid, otherwise false. | 43 | * @return True if the command is valid, otherwise false. |
| 43 | */ | 44 | */ |
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 45 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 45 | 46 | ||
| 46 | /// Input mix buffer offsets for each channel | 47 | /// Input mix buffer offsets for each channel |
| 47 | std::array<s16, MaxChannels> inputs; | 48 | std::array<s16, MaxChannels> inputs; |
| @@ -59,4 +60,4 @@ struct ReverbCommand : ICommand { | |||
| 59 | bool long_size_pre_delay_supported; | 60 | bool long_size_pre_delay_supported; |
| 60 | }; | 61 | }; |
| 61 | 62 | ||
| 62 | } // namespace AudioCore::AudioRenderer | 63 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h index f2dd41254..10a78ddf2 100644 --- a/src/audio_core/renderer/command/icommand.h +++ b/src/audio_core/renderer/command/icommand.h | |||
| @@ -3,14 +3,18 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <string> | ||
| 7 | |||
| 6 | #include "audio_core/common/common.h" | 8 | #include "audio_core/common/common.h" |
| 7 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 8 | 10 | ||
| 9 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 10 | namespace ADSP { | ||
| 11 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 12 | } | 13 | } |
| 13 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | using namespace ::AudioCore::ADSP; | ||
| 17 | |||
| 14 | enum class CommandId : u8 { | 18 | enum class CommandId : u8 { |
| 15 | /* 0x00 */ Invalid, | 19 | /* 0x00 */ Invalid, |
| 16 | /* 0x01 */ DataSourcePcmInt16Version1, | 20 | /* 0x01 */ DataSourcePcmInt16Version1, |
| @@ -59,14 +63,15 @@ struct ICommand { | |||
| 59 | * @param processor - The CommandListProcessor processing this command. | 63 | * @param processor - The CommandListProcessor processing this command. |
| 60 | * @param string - The string to print into. | 64 | * @param string - The string to print into. |
| 61 | */ | 65 | */ |
| 62 | virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0; | 66 | virtual void Dump(const AudioRenderer::CommandListProcessor& processor, |
| 67 | std::string& string) = 0; | ||
| 63 | 68 | ||
| 64 | /** | 69 | /** |
| 65 | * Process this command. | 70 | * Process this command. |
| 66 | * | 71 | * |
| 67 | * @param processor - The CommandListProcessor processing this command. | 72 | * @param processor - The CommandListProcessor processing this command. |
| 68 | */ | 73 | */ |
| 69 | virtual void Process(const ADSP::CommandListProcessor& processor) = 0; | 74 | virtual void Process(const AudioRenderer::CommandListProcessor& processor) = 0; |
| 70 | 75 | ||
| 71 | /** | 76 | /** |
| 72 | * Verify this command's data is valid. | 77 | * Verify this command's data is valid. |
| @@ -74,7 +79,7 @@ struct ICommand { | |||
| 74 | * @param processor - The CommandListProcessor processing this command. | 79 | * @param processor - The CommandListProcessor processing this command. |
| 75 | * @return True if the command is valid, otherwise false. | 80 | * @return True if the command is valid, otherwise false. |
| 76 | */ | 81 | */ |
| 77 | virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0; | 82 | virtual bool Verify(const AudioRenderer::CommandListProcessor& processor) = 0; |
| 78 | 83 | ||
| 79 | /// Command magic 0xCAFEBABE | 84 | /// Command magic 0xCAFEBABE |
| 80 | u32 magic{}; | 85 | u32 magic{}; |
| @@ -90,4 +95,4 @@ struct ICommand { | |||
| 90 | u32 node_id{}; | 95 | u32 node_id{}; |
| 91 | }; | 96 | }; |
| 92 | 97 | ||
| 93 | } // namespace AudioCore::AudioRenderer | 98 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp index 4f649d6a8..060d7cb28 100644 --- a/src/audio_core/renderer/command/mix/clear_mix.cpp +++ b/src/audio_core/renderer/command/mix/clear_mix.cpp | |||
| @@ -3,22 +3,22 @@ | |||
| 3 | 3 | ||
| 4 | #include <string> | 4 | #include <string> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/mix/clear_mix.h" | 7 | #include "audio_core/renderer/command/mix/clear_mix.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 11 | void ClearMixBufferCommand::Dump( |
| 12 | std::string& string) { | 12 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 13 | string += fmt::format("ClearMixBufferCommand\n"); | 13 | string += fmt::format("ClearMixBufferCommand\n"); |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { | 16 | void ClearMixBufferCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 17 | memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); | 17 | memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); |
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { | 20 | bool ClearMixBufferCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 21 | return true; | 21 | return true; |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | } // namespace AudioCore::AudioRenderer | 24 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h index 956ec0b65..650fa1a8a 100644 --- a/src/audio_core/renderer/command/mix/clear_mix.h +++ b/src/audio_core/renderer/command/mix/clear_mix.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for a clearing the mix buffers. | 18 | * AudioRenderer command for a clearing the mix buffers. |
| 18 | * Used at the start of each command list. | 19 | * Used at the start of each command list. |
| @@ -24,14 +25,14 @@ struct ClearMixBufferCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct ClearMixBufferCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | }; | 44 | }; |
| 44 | 45 | ||
| 45 | } // namespace AudioCore::AudioRenderer | 46 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp index 1d49f1644..5d386f95a 100644 --- a/src/audio_core/renderer/command/mix/copy_mix.cpp +++ b/src/audio_core/renderer/command/mix/copy_mix.cpp | |||
| @@ -1,18 +1,18 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/mix/copy_mix.h" | 5 | #include "audio_core/renderer/command/mix/copy_mix.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 9 | void CopyMixBufferCommand::Dump( |
| 10 | std::string& string) { | 10 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 11 | string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, | 11 | string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, |
| 12 | output_index); | 12 | output_index); |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { | 15 | void CopyMixBufferCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 16 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | 16 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 17 | processor.sample_count)}; | 17 | processor.sample_count)}; |
| 18 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | 18 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, |
| @@ -20,8 +20,8 @@ void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) | |||
| 20 | std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); | 20 | std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { | 23 | bool CopyMixBufferCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 24 | return true; | 24 | return true; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | } // namespace AudioCore::AudioRenderer | 27 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h index a59007fb6..ae247c3f8 100644 --- a/src/audio_core/renderer/command/mix/copy_mix.h +++ b/src/audio_core/renderer/command/mix/copy_mix.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for a copying a mix buffer from input to output. | 18 | * AudioRenderer command for a copying a mix buffer from input to output. |
| 18 | */ | 19 | */ |
| @@ -23,14 +24,14 @@ struct CopyMixBufferCommand : ICommand { | |||
| 23 | * @param processor - The CommandListProcessor processing this command. | 24 | * @param processor - The CommandListProcessor processing this command. |
| 24 | * @param string - The string to print into. | 25 | * @param string - The string to print into. |
| 25 | */ | 26 | */ |
| 26 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 27 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 27 | 28 | ||
| 28 | /** | 29 | /** |
| 29 | * Process this command. | 30 | * Process this command. |
| 30 | * | 31 | * |
| 31 | * @param processor - The CommandListProcessor processing this command. | 32 | * @param processor - The CommandListProcessor processing this command. |
| 32 | */ | 33 | */ |
| 33 | void Process(const ADSP::CommandListProcessor& processor) override; | 34 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 34 | 35 | ||
| 35 | /** | 36 | /** |
| 36 | * Verify this command's data is valid. | 37 | * Verify this command's data is valid. |
| @@ -38,7 +39,7 @@ struct CopyMixBufferCommand : ICommand { | |||
| 38 | * @param processor - The CommandListProcessor processing this command. | 39 | * @param processor - The CommandListProcessor processing this command. |
| 39 | * @return True if the command is valid, otherwise false. | 40 | * @return True if the command is valid, otherwise false. |
| 40 | */ | 41 | */ |
| 41 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 42 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 42 | 43 | ||
| 43 | /// Input mix buffer index | 44 | /// Input mix buffer index |
| 44 | s16 input_index; | 45 | s16 input_index; |
| @@ -46,4 +47,4 @@ struct CopyMixBufferCommand : ICommand { | |||
| 46 | s16 output_index; | 47 | s16 output_index; |
| 47 | }; | 48 | }; |
| 48 | 49 | ||
| 49 | } // namespace AudioCore::AudioRenderer | 50 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp index c2bc10061..caedb56b7 100644 --- a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp | |||
| @@ -1,11 +1,11 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" | ||
| 4 | #include "audio_core/common/common.h" | 5 | #include "audio_core/common/common.h" |
| 5 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 6 | #include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" | 6 | #include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | /** | 9 | /** |
| 10 | * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time | 10 | * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time |
| 11 | * according to decay. | 11 | * according to decay. |
| @@ -36,13 +36,13 @@ static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample, | |||
| 36 | } | 36 | } |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 39 | void DepopForMixBuffersCommand::Dump( |
| 40 | std::string& string) { | 40 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 41 | string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, | 41 | string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, |
| 42 | count, decay.to_float()); | 42 | count, decay.to_float()); |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) { | 45 | void DepopForMixBuffersCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 46 | auto end_index{std::min(processor.buffer_count, input + count)}; | 46 | auto end_index{std::min(processor.buffer_count, input + count)}; |
| 47 | std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index}; | 47 | std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index}; |
| 48 | 48 | ||
| @@ -57,8 +57,8 @@ void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& proces | |||
| 57 | } | 57 | } |
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) { | 60 | bool DepopForMixBuffersCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 61 | return true; | 61 | return true; |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | } // namespace AudioCore::AudioRenderer | 64 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h index e7268ff27..699d38988 100644 --- a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h | |||
| @@ -9,11 +9,12 @@ | |||
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/fixed_point.h" | 10 | #include "common/fixed_point.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::ADSP::AudioRenderer { |
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | 13 | class CommandListProcessor; |
| 15 | } | 14 | } |
| 16 | 15 | ||
| 16 | namespace AudioCore::Renderer { | ||
| 17 | |||
| 17 | /** | 18 | /** |
| 18 | * AudioRenderer command for depopping a mix buffer. | 19 | * AudioRenderer command for depopping a mix buffer. |
| 19 | * Adds a cumulation of previous samples to the current mix buffer with a decay. | 20 | * Adds a cumulation of previous samples to the current mix buffer with a decay. |
| @@ -25,14 +26,14 @@ struct DepopForMixBuffersCommand : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,7 +41,7 @@ struct DepopForMixBuffersCommand : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Starting input mix buffer index | 46 | /// Starting input mix buffer index |
| 46 | u32 input; | 47 | u32 input; |
| @@ -52,4 +53,4 @@ struct DepopForMixBuffersCommand : ICommand { | |||
| 52 | CpuAddr depop_buffer; | 53 | CpuAddr depop_buffer; |
| 53 | }; | 54 | }; |
| 54 | 55 | ||
| 55 | } // namespace AudioCore::AudioRenderer | 56 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp index 69bb78ccc..2faf4681a 100644 --- a/src/audio_core/renderer/command/mix/depop_prepare.cpp +++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp | |||
| @@ -1,15 +1,15 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/mix/depop_prepare.h" | 5 | #include "audio_core/renderer/command/mix/depop_prepare.h" |
| 6 | #include "audio_core/renderer/voice/voice_state.h" | 6 | #include "audio_core/renderer/voice/voice_state.h" |
| 7 | #include "common/fixed_point.h" | 7 | #include "common/fixed_point.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 11 | void DepopPrepareCommand::Dump( |
| 12 | std::string& string) { | 12 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 13 | string += fmt::format("DepopPrepareCommand\n\tinputs: "); | 13 | string += fmt::format("DepopPrepareCommand\n\tinputs: "); |
| 14 | for (u32 i = 0; i < buffer_count; i++) { | 14 | for (u32 i = 0; i < buffer_count; i++) { |
| 15 | string += fmt::format("{:02X}, ", inputs[i]); | 15 | string += fmt::format("{:02X}, ", inputs[i]); |
| @@ -17,7 +17,7 @@ void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor | |||
| 17 | string += "\n"; | 17 | string += "\n"; |
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { | 20 | void DepopPrepareCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 21 | auto samples{reinterpret_cast<s32*>(previous_samples)}; | 21 | auto samples{reinterpret_cast<s32*>(previous_samples)}; |
| 22 | auto buffer{reinterpret_cast<s32*>(depop_buffer)}; | 22 | auto buffer{reinterpret_cast<s32*>(depop_buffer)}; |
| 23 | 23 | ||
| @@ -29,8 +29,8 @@ void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 29 | } | 29 | } |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) { | 32 | bool DepopPrepareCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 33 | return true; | 33 | return true; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | } // namespace AudioCore::AudioRenderer | 36 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h index a5465da9a..161a94461 100644 --- a/src/audio_core/renderer/command/mix/depop_prepare.h +++ b/src/audio_core/renderer/command/mix/depop_prepare.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for preparing depop. | 18 | * AudioRenderer command for preparing depop. |
| 18 | * Adds the previusly output last samples to the depop buffer. | 19 | * Adds the previusly output last samples to the depop buffer. |
| @@ -24,14 +25,14 @@ struct DepopPrepareCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct DepopPrepareCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | 44 | ||
| 44 | /// Depop buffer offset for each mix buffer | 45 | /// Depop buffer offset for each mix buffer |
| 45 | std::array<s16, MaxMixBuffers> inputs; | 46 | std::array<s16, MaxMixBuffers> inputs; |
| @@ -51,4 +52,4 @@ struct DepopPrepareCommand : ICommand { | |||
| 51 | CpuAddr depop_buffer; | 52 | CpuAddr depop_buffer; |
| 52 | }; | 53 | }; |
| 53 | 54 | ||
| 54 | } // namespace AudioCore::AudioRenderer | 55 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp index 8ecf9b05a..8bd689b88 100644 --- a/src/audio_core/renderer/command/mix/mix.cpp +++ b/src/audio_core/renderer/command/mix/mix.cpp | |||
| @@ -5,11 +5,11 @@ | |||
| 5 | #include <limits> | 5 | #include <limits> |
| 6 | #include <span> | 6 | #include <span> |
| 7 | 7 | ||
| 8 | #include "audio_core/renderer/adsp/command_list_processor.h" | 8 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 9 | #include "audio_core/renderer/command/mix/mix.h" | 9 | #include "audio_core/renderer/command/mix/mix.h" |
| 10 | #include "common/fixed_point.h" | 10 | #include "common/fixed_point.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * Mix input mix buffer into output mix buffer, with volume applied to the input. | 14 | * Mix input mix buffer into output mix buffer, with volume applied to the input. |
| 15 | * | 15 | * |
| @@ -28,7 +28,7 @@ static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f3 | |||
| 28 | } | 28 | } |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 31 | void MixCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 32 | std::string& string) { | 32 | std::string& string) { |
| 33 | string += fmt::format("MixCommand"); | 33 | string += fmt::format("MixCommand"); |
| 34 | string += fmt::format("\n\tinput {:02X}", input_index); | 34 | string += fmt::format("\n\tinput {:02X}", input_index); |
| @@ -37,7 +37,7 @@ void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& process | |||
| 37 | string += "\n"; | 37 | string += "\n"; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | void MixCommand::Process(const ADSP::CommandListProcessor& processor) { | 40 | void MixCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 41 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | 41 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 42 | processor.sample_count)}; | 42 | processor.sample_count)}; |
| 43 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | 43 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, |
| @@ -63,8 +63,8 @@ void MixCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 63 | } | 63 | } |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) { | 66 | bool MixCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 67 | return true; | 67 | return true; |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | } // namespace AudioCore::AudioRenderer | 70 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h index 0201cf171..64c812382 100644 --- a/src/audio_core/renderer/command/mix/mix.h +++ b/src/audio_core/renderer/command/mix/mix.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume | 18 | * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume |
| 18 | * applied to the input. | 19 | * applied to the input. |
| @@ -24,14 +25,14 @@ struct MixCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct MixCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | 44 | ||
| 44 | /// Fixed point precision | 45 | /// Fixed point precision |
| 45 | u8 precision; | 46 | u8 precision; |
| @@ -51,4 +52,4 @@ struct MixCommand : ICommand { | |||
| 51 | f32 volume; | 52 | f32 volume; |
| 52 | }; | 53 | }; |
| 53 | 54 | ||
| 54 | } // namespace AudioCore::AudioRenderer | 55 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp index d67123cd8..2f6500da5 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp.cpp +++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/mix/mix_ramp.h" | 5 | #include "audio_core/renderer/command/mix/mix_ramp.h" |
| 6 | #include "common/fixed_point.h" | 6 | #include "common/fixed_point.h" |
| 7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | template <size_t Q> | 11 | template <size_t Q> |
| 12 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | 12 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, |
| @@ -33,7 +33,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo | |||
| 33 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32); | 33 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32); |
| 34 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32); | 34 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32); |
| 35 | 35 | ||
| 36 | void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | 36 | void MixRampCommand::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 37 | std::string& string) { | ||
| 37 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | 38 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; |
| 38 | string += fmt::format("MixRampCommand"); | 39 | string += fmt::format("MixRampCommand"); |
| 39 | string += fmt::format("\n\tinput {:02X}", input_index); | 40 | string += fmt::format("\n\tinput {:02X}", input_index); |
| @@ -44,7 +45,7 @@ void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::stri | |||
| 44 | string += "\n"; | 45 | string += "\n"; |
| 45 | } | 46 | } |
| 46 | 47 | ||
| 47 | void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { | 48 | void MixRampCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 48 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | 49 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 49 | processor.sample_count)}; | 50 | processor.sample_count)}; |
| 50 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | 51 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, |
| @@ -75,8 +76,8 @@ void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 75 | } | 76 | } |
| 76 | } | 77 | } |
| 77 | 78 | ||
| 78 | bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) { | 79 | bool MixRampCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 79 | return true; | 80 | return true; |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | } // namespace AudioCore::AudioRenderer | 83 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h index 52f74a273..92209b53a 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp.h +++ b/src/audio_core/renderer/command/mix/mix_ramp.h | |||
| @@ -9,11 +9,12 @@ | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | 9 | #include "audio_core/renderer/command/icommand.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::ADSP::AudioRenderer { |
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | 13 | class CommandListProcessor; |
| 15 | } | 14 | } |
| 16 | 15 | ||
| 16 | namespace AudioCore::Renderer { | ||
| 17 | |||
| 17 | /** | 18 | /** |
| 18 | * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume | 19 | * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume |
| 19 | * applied to the input, and volume ramping to smooth out the transition. | 20 | * applied to the input, and volume ramping to smooth out the transition. |
| @@ -25,14 +26,14 @@ struct MixRampCommand : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,7 +41,7 @@ struct MixRampCommand : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Fixed point precision | 46 | /// Fixed point precision |
| 46 | u8 precision; | 47 | u8 precision; |
| @@ -70,4 +71,4 @@ template <size_t Q> | |||
| 70 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_, | 71 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_, |
| 71 | u32 sample_count); | 72 | u32 sample_count); |
| 72 | 73 | ||
| 73 | } // namespace AudioCore::AudioRenderer | 74 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp index 43dbef9fc..64138a9bf 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp | |||
| @@ -1,13 +1,14 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/mix/mix_ramp.h" | 5 | #include "audio_core/renderer/command/mix/mix_ramp.h" |
| 6 | #include "audio_core/renderer/command/mix/mix_ramp_grouped.h" | 6 | #include "audio_core/renderer/command/mix/mix_ramp_grouped.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | 10 | void MixRampGroupedCommand::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 11 | std::string& string) { | ||
| 11 | string += "MixRampGroupedCommand"; | 12 | string += "MixRampGroupedCommand"; |
| 12 | for (u32 i = 0; i < buffer_count; i++) { | 13 | for (u32 i = 0; i < buffer_count; i++) { |
| 13 | string += fmt::format("\n\t{}", i); | 14 | string += fmt::format("\n\t{}", i); |
| @@ -21,7 +22,7 @@ void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, st | |||
| 21 | } | 22 | } |
| 22 | } | 23 | } |
| 23 | 24 | ||
| 24 | void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) { | 25 | void MixRampGroupedCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 25 | std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers}; | 26 | std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers}; |
| 26 | 27 | ||
| 27 | for (u32 i = 0; i < buffer_count; i++) { | 28 | for (u32 i = 0; i < buffer_count; i++) { |
| @@ -58,8 +59,8 @@ void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) | |||
| 58 | } | 59 | } |
| 59 | } | 60 | } |
| 60 | 61 | ||
| 61 | bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) { | 62 | bool MixRampGroupedCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 62 | return true; | 63 | return true; |
| 63 | } | 64 | } |
| 64 | 65 | ||
| 65 | } // namespace AudioCore::AudioRenderer | 66 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h index 3b0ce67ef..9621e42a3 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h | |||
| @@ -9,11 +9,12 @@ | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | 9 | #include "audio_core/renderer/command/icommand.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::ADSP::AudioRenderer { |
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | 13 | class CommandListProcessor; |
| 15 | } | 14 | } |
| 16 | 15 | ||
| 16 | namespace AudioCore::Renderer { | ||
| 17 | |||
| 17 | /** | 18 | /** |
| 18 | * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with | 19 | * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with |
| 19 | * a volume applied to the input, and volume ramping to smooth out the transition. | 20 | * a volume applied to the input, and volume ramping to smooth out the transition. |
| @@ -25,14 +26,14 @@ struct MixRampGroupedCommand : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,7 +41,7 @@ struct MixRampGroupedCommand : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Fixed point precision | 46 | /// Fixed point precision |
| 46 | u8 precision; | 47 | u8 precision; |
| @@ -58,4 +59,4 @@ struct MixRampGroupedCommand : ICommand { | |||
| 58 | CpuAddr previous_samples; | 59 | CpuAddr previous_samples; |
| 59 | }; | 60 | }; |
| 60 | 61 | ||
| 61 | } // namespace AudioCore::AudioRenderer | 62 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp index b045fb062..92baf6cc3 100644 --- a/src/audio_core/renderer/command/mix/volume.cpp +++ b/src/audio_core/renderer/command/mix/volume.cpp | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/mix/volume.h" | 5 | #include "audio_core/renderer/command/mix/volume.h" |
| 6 | #include "common/fixed_point.h" | 6 | #include "common/fixed_point.h" |
| 7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Apply volume to the input mix buffer, saving to the output buffer. | 11 | * Apply volume to the input mix buffer, saving to the output buffer. |
| 12 | * | 12 | * |
| @@ -29,7 +29,7 @@ static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, | |||
| 29 | } | 29 | } |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 32 | void VolumeCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 33 | std::string& string) { | 33 | std::string& string) { |
| 34 | string += fmt::format("VolumeCommand"); | 34 | string += fmt::format("VolumeCommand"); |
| 35 | string += fmt::format("\n\tinput {:02X}", input_index); | 35 | string += fmt::format("\n\tinput {:02X}", input_index); |
| @@ -38,7 +38,7 @@ void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc | |||
| 38 | string += "\n"; | 38 | string += "\n"; |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { | 41 | void VolumeCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 42 | // If input and output buffers are the same, and the volume is 1.0f, this won't do | 42 | // If input and output buffers are the same, and the volume is 1.0f, this won't do |
| 43 | // anything, so just skip. | 43 | // anything, so just skip. |
| 44 | if (input_index == output_index && volume == 1.0f) { | 44 | if (input_index == output_index && volume == 1.0f) { |
| @@ -65,8 +65,8 @@ void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 65 | } | 65 | } |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) { | 68 | bool VolumeCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 69 | return true; | 69 | return true; |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | } // namespace AudioCore::AudioRenderer | 72 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h index 6ae9fb794..fbb8156ca 100644 --- a/src/audio_core/renderer/command/mix/volume.h +++ b/src/audio_core/renderer/command/mix/volume.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for applying volume to a mix buffer. | 18 | * AudioRenderer command for applying volume to a mix buffer. |
| 18 | */ | 19 | */ |
| @@ -23,14 +24,14 @@ struct VolumeCommand : ICommand { | |||
| 23 | * @param processor - The CommandListProcessor processing this command. | 24 | * @param processor - The CommandListProcessor processing this command. |
| 24 | * @param string - The string to print into. | 25 | * @param string - The string to print into. |
| 25 | */ | 26 | */ |
| 26 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 27 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 27 | 28 | ||
| 28 | /** | 29 | /** |
| 29 | * Process this command. | 30 | * Process this command. |
| 30 | * | 31 | * |
| 31 | * @param processor - The CommandListProcessor processing this command. | 32 | * @param processor - The CommandListProcessor processing this command. |
| 32 | */ | 33 | */ |
| 33 | void Process(const ADSP::CommandListProcessor& processor) override; | 34 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 34 | 35 | ||
| 35 | /** | 36 | /** |
| 36 | * Verify this command's data is valid. | 37 | * Verify this command's data is valid. |
| @@ -38,7 +39,7 @@ struct VolumeCommand : ICommand { | |||
| 38 | * @param processor - The CommandListProcessor processing this command. | 39 | * @param processor - The CommandListProcessor processing this command. |
| 39 | * @return True if the command is valid, otherwise false. | 40 | * @return True if the command is valid, otherwise false. |
| 40 | */ | 41 | */ |
| 41 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 42 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 42 | 43 | ||
| 43 | /// Fixed point precision | 44 | /// Fixed point precision |
| 44 | u8 precision; | 45 | u8 precision; |
| @@ -50,4 +51,4 @@ struct VolumeCommand : ICommand { | |||
| 50 | f32 volume; | 51 | f32 volume; |
| 51 | }; | 52 | }; |
| 52 | 53 | ||
| 53 | } // namespace AudioCore::AudioRenderer | 54 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp index 424307148..fdc751957 100644 --- a/src/audio_core/renderer/command/mix/volume_ramp.cpp +++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp | |||
| @@ -1,11 +1,11 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/mix/volume_ramp.h" | 5 | #include "audio_core/renderer/command/mix/volume_ramp.h" |
| 6 | #include "common/fixed_point.h" | 6 | #include "common/fixed_point.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | /** | 9 | /** |
| 10 | * Apply volume with ramping to the input mix buffer, saving to the output buffer. | 10 | * Apply volume with ramping to the input mix buffer, saving to the output buffer. |
| 11 | * | 11 | * |
| @@ -38,7 +38,8 @@ static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> | |||
| 38 | } | 38 | } |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | 41 | void VolumeRampCommand::Dump(const AudioRenderer::CommandListProcessor& processor, |
| 42 | std::string& string) { | ||
| 42 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | 43 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; |
| 43 | string += fmt::format("VolumeRampCommand"); | 44 | string += fmt::format("VolumeRampCommand"); |
| 44 | string += fmt::format("\n\tinput {:02X}", input_index); | 45 | string += fmt::format("\n\tinput {:02X}", input_index); |
| @@ -49,7 +50,7 @@ void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::s | |||
| 49 | string += "\n"; | 50 | string += "\n"; |
| 50 | } | 51 | } |
| 51 | 52 | ||
| 52 | void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { | 53 | void VolumeRampCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 53 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | 54 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 54 | processor.sample_count)}; | 55 | processor.sample_count)}; |
| 55 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | 56 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, |
| @@ -77,8 +78,8 @@ void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 77 | } | 78 | } |
| 78 | } | 79 | } |
| 79 | 80 | ||
| 80 | bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) { | 81 | bool VolumeRampCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 81 | return true; | 82 | return true; |
| 82 | } | 83 | } |
| 83 | 84 | ||
| 84 | } // namespace AudioCore::AudioRenderer | 85 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h index 77b61547e..d9794fb95 100644 --- a/src/audio_core/renderer/command/mix/volume_ramp.h +++ b/src/audio_core/renderer/command/mix/volume_ramp.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth | 18 | * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth |
| 18 | * out the transition. | 19 | * out the transition. |
| @@ -24,14 +25,14 @@ struct VolumeRampCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct VolumeRampCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | 44 | ||
| 44 | /// Fixed point precision | 45 | /// Fixed point precision |
| 45 | u8 precision; | 46 | u8 precision; |
| @@ -53,4 +54,4 @@ struct VolumeRampCommand : ICommand { | |||
| 53 | f32 volume; | 54 | f32 volume; |
| 54 | }; | 55 | }; |
| 55 | 56 | ||
| 56 | } // namespace AudioCore::AudioRenderer | 57 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp index 4a881547f..f0cfcc9fd 100644 --- a/src/audio_core/renderer/command/performance/performance.cpp +++ b/src/audio_core/renderer/command/performance/performance.cpp | |||
| @@ -1,25 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/performance/performance.h" | 5 | #include "audio_core/renderer/command/performance/performance.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/core_timing.h" | 7 | #include "core/core_timing.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 11 | void PerformanceCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 12 | std::string& string) { | 12 | std::string& string) { |
| 13 | string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state)); | 13 | string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state)); |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { | 16 | void PerformanceCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 17 | auto base{entry_address.translated_address}; | 17 | auto base{entry_address.translated_address}; |
| 18 | if (state == PerformanceState::Start) { | 18 | if (state == PerformanceState::Start) { |
| 19 | auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; | 19 | auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; |
| 20 | *start_time_ptr = | 20 | *start_time_ptr = |
| 21 | static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time - | 21 | static_cast<u32>(processor.system->CoreTiming().GetGlobalTimeUs().count() - |
| 22 | processor.current_processing_time); | 22 | processor.start_time - processor.current_processing_time); |
| 23 | } else if (state == PerformanceState::Stop) { | 23 | } else if (state == PerformanceState::Stop) { |
| 24 | auto processed_time_ptr{ | 24 | auto processed_time_ptr{ |
| 25 | reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; | 25 | reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; |
| @@ -27,14 +27,14 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 27 | reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; | 27 | reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; |
| 28 | 28 | ||
| 29 | *processed_time_ptr = | 29 | *processed_time_ptr = |
| 30 | static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time - | 30 | static_cast<u32>(processor.system->CoreTiming().GetGlobalTimeUs().count() - |
| 31 | processor.current_processing_time); | 31 | processor.start_time - processor.current_processing_time); |
| 32 | (*entry_count_ptr)++; | 32 | (*entry_count_ptr)++; |
| 33 | } | 33 | } |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) { | 36 | bool PerformanceCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 37 | return true; | 37 | return true; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | } // namespace AudioCore::AudioRenderer | 40 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h index 11a7d6c08..522e51e34 100644 --- a/src/audio_core/renderer/command/performance/performance.h +++ b/src/audio_core/renderer/command/performance/performance.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/performance/performance_manager.h" | 10 | #include "audio_core/renderer/performance/performance_manager.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. | 20 | * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. |
| 20 | */ | 21 | */ |
| @@ -25,14 +26,14 @@ struct PerformanceCommand : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,7 +41,7 @@ struct PerformanceCommand : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// State of the performance | 46 | /// State of the performance |
| 46 | PerformanceState state; | 47 | PerformanceState state; |
| @@ -48,4 +49,4 @@ struct PerformanceCommand : ICommand { | |||
| 48 | PerformanceEntryAddresses entry_address; | 49 | PerformanceEntryAddresses entry_address; |
| 49 | }; | 50 | }; |
| 50 | 51 | ||
| 51 | } // namespace AudioCore::AudioRenderer | 52 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp index 1fd90308a..f9b289887 100644 --- a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp | |||
| @@ -1,13 +1,13 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | 4 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 5 | #include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" | 5 | #include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 9 | void DownMix6chTo2chCommand::Dump( |
| 10 | std::string& string) { | 10 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 11 | string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); | 11 | string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); |
| 12 | for (u32 i = 0; i < MaxChannels; i++) { | 12 | for (u32 i = 0; i < MaxChannels; i++) { |
| 13 | string += fmt::format("{:02X}, ", inputs[i]); | 13 | string += fmt::format("{:02X}, ", inputs[i]); |
| @@ -19,7 +19,7 @@ void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProces | |||
| 19 | string += "\n"; | 19 | string += "\n"; |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) { | 22 | void DownMix6chTo2chCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 23 | auto in_front_left{ | 23 | auto in_front_left{ |
| 24 | processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; | 24 | processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; |
| 25 | auto in_front_right{ | 25 | auto in_front_right{ |
| @@ -67,8 +67,8 @@ void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor | |||
| 67 | std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); | 67 | std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) { | 70 | bool DownMix6chTo2chCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 71 | return true; | 71 | return true; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | } // namespace AudioCore::AudioRenderer | 74 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h index dc133a73b..96cbc5506 100644 --- a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h | |||
| @@ -9,11 +9,12 @@ | |||
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/fixed_point.h" | 10 | #include "common/fixed_point.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::ADSP::AudioRenderer { |
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | 13 | class CommandListProcessor; |
| 15 | } | 14 | } |
| 16 | 15 | ||
| 16 | namespace AudioCore::Renderer { | ||
| 17 | |||
| 17 | /** | 18 | /** |
| 18 | * AudioRenderer command for downmixing 6 channels to 2. | 19 | * AudioRenderer command for downmixing 6 channels to 2. |
| 19 | * Channel layout (SMPTE): | 20 | * Channel layout (SMPTE): |
| @@ -31,14 +32,14 @@ struct DownMix6chTo2chCommand : ICommand { | |||
| 31 | * @param processor - The CommandListProcessor processing this command. | 32 | * @param processor - The CommandListProcessor processing this command. |
| 32 | * @param string - The string to print into. | 33 | * @param string - The string to print into. |
| 33 | */ | 34 | */ |
| 34 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 35 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Process this command. | 38 | * Process this command. |
| 38 | * | 39 | * |
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | */ | 41 | */ |
| 41 | void Process(const ADSP::CommandListProcessor& processor) override; | 42 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 42 | 43 | ||
| 43 | /** | 44 | /** |
| 44 | * Verify this command's data is valid. | 45 | * Verify this command's data is valid. |
| @@ -46,7 +47,7 @@ struct DownMix6chTo2chCommand : ICommand { | |||
| 46 | * @param processor - The CommandListProcessor processing this command. | 47 | * @param processor - The CommandListProcessor processing this command. |
| 47 | * @return True if the command is valid, otherwise false. | 48 | * @return True if the command is valid, otherwise false. |
| 48 | */ | 49 | */ |
| 49 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 50 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 50 | 51 | ||
| 51 | /// Input mix buffer offsets for each channel | 52 | /// Input mix buffer offsets for each channel |
| 52 | std::array<s16, MaxChannels> inputs; | 53 | std::array<s16, MaxChannels> inputs; |
| @@ -56,4 +57,4 @@ struct DownMix6chTo2chCommand : ICommand { | |||
| 56 | std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff; | 57 | std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff; |
| 57 | }; | 58 | }; |
| 58 | 59 | ||
| 59 | } // namespace AudioCore::AudioRenderer | 60 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp index 070c9d2b8..51f4ba39e 100644 --- a/src/audio_core/renderer/command/resample/resample.cpp +++ b/src/audio_core/renderer/command/resample/resample.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/command/resample/resample.h" | 4 | #include "audio_core/renderer/command/resample/resample.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, | 8 | static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, |
| 9 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | 9 | const Common::FixedPoint<49, 15>& sample_rate_ratio, |
| @@ -880,4 +880,4 @@ void Resample(std::span<s32> output, std::span<const s16> input, | |||
| 880 | } | 880 | } |
| 881 | } | 881 | } |
| 882 | 882 | ||
| 883 | } // namespace AudioCore::AudioRenderer | 883 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h index ba9209b82..134aff0c9 100644 --- a/src/audio_core/renderer/command/resample/resample.h +++ b/src/audio_core/renderer/command/resample/resample.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/fixed_point.h" | 10 | #include "common/fixed_point.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * Resample an input buffer into an output buffer, according to the sample_rate_ratio. | 14 | * Resample an input buffer into an output buffer, according to the sample_rate_ratio. |
| 15 | * | 15 | * |
| @@ -26,4 +26,4 @@ void Resample(std::span<s32> output, std::span<const s16> input, | |||
| 26 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | 26 | const Common::FixedPoint<49, 15>& sample_rate_ratio, |
| 27 | Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); | 27 | Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); |
| 28 | 28 | ||
| 29 | } // namespace AudioCore::AudioRenderer | 29 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp index 86ddee1a4..691d70390 100644 --- a/src/audio_core/renderer/command/resample/upsample.cpp +++ b/src/audio_core/renderer/command/resample/upsample.cpp | |||
| @@ -3,11 +3,11 @@ | |||
| 3 | 3 | ||
| 4 | #include <array> | 4 | #include <array> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/resample/upsample.h" | 7 | #include "audio_core/renderer/command/resample/upsample.h" |
| 8 | #include "audio_core/renderer/upsampler/upsampler_info.h" | 8 | #include "audio_core/renderer/upsampler/upsampler_info.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | /** | 11 | /** |
| 12 | * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. | 12 | * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. |
| 13 | * | 13 | * |
| @@ -198,7 +198,7 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input, | |||
| 198 | } | 198 | } |
| 199 | } | 199 | } |
| 200 | 200 | ||
| 201 | auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 201 | auto UpsampleCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 202 | std::string& string) -> void { | 202 | std::string& string) -> void { |
| 203 | string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", | 203 | string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", |
| 204 | source_sample_count, source_sample_rate); | 204 | source_sample_count, source_sample_rate); |
| @@ -213,7 +213,7 @@ auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& pr | |||
| 213 | string += "\n"; | 213 | string += "\n"; |
| 214 | } | 214 | } |
| 215 | 215 | ||
| 216 | void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { | 216 | void UpsampleCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 217 | const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; | 217 | const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; |
| 218 | const auto input_count{std::min(info->input_count, buffer_count)}; | 218 | const auto input_count{std::min(info->input_count, buffer_count)}; |
| 219 | const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count}; | 219 | const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count}; |
| @@ -234,8 +234,8 @@ void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 234 | } | 234 | } |
| 235 | } | 235 | } |
| 236 | 236 | ||
| 237 | bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) { | 237 | bool UpsampleCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 238 | return true; | 238 | return true; |
| 239 | } | 239 | } |
| 240 | 240 | ||
| 241 | } // namespace AudioCore::AudioRenderer | 241 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h index bfc94e8af..877271ba9 100644 --- a/src/audio_core/renderer/command/resample/upsample.h +++ b/src/audio_core/renderer/command/resample/upsample.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for upsampling a mix buffer to 48Khz. | 18 | * AudioRenderer command for upsampling a mix buffer to 48Khz. |
| 18 | * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. | 19 | * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. |
| @@ -24,14 +25,14 @@ struct UpsampleCommand : ICommand { | |||
| 24 | * @param processor - The CommandListProcessor processing this command. | 25 | * @param processor - The CommandListProcessor processing this command. |
| 25 | * @param string - The string to print into. | 26 | * @param string - The string to print into. |
| 26 | */ | 27 | */ |
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 28 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 28 | 29 | ||
| 29 | /** | 30 | /** |
| 30 | * Process this command. | 31 | * Process this command. |
| 31 | * | 32 | * |
| 32 | * @param processor - The CommandListProcessor processing this command. | 33 | * @param processor - The CommandListProcessor processing this command. |
| 33 | */ | 34 | */ |
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | 35 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 35 | 36 | ||
| 36 | /** | 37 | /** |
| 37 | * Verify this command's data is valid. | 38 | * Verify this command's data is valid. |
| @@ -39,7 +40,7 @@ struct UpsampleCommand : ICommand { | |||
| 39 | * @param processor - The CommandListProcessor processing this command. | 40 | * @param processor - The CommandListProcessor processing this command. |
| 40 | * @return True if the command is valid, otherwise false. | 41 | * @return True if the command is valid, otherwise false. |
| 41 | */ | 42 | */ |
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 43 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 43 | 44 | ||
| 44 | /// Pointer to the output samples buffer. | 45 | /// Pointer to the output samples buffer. |
| 45 | CpuAddr samples_buffer; | 46 | CpuAddr samples_buffer; |
| @@ -57,4 +58,4 @@ struct UpsampleCommand : ICommand { | |||
| 57 | CpuAddr upsampler_info; | 58 | CpuAddr upsampler_info; |
| 58 | }; | 59 | }; |
| 59 | 60 | ||
| 60 | } // namespace AudioCore::AudioRenderer | 61 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp index e2ce59792..e056d15a6 100644 --- a/src/audio_core/renderer/command/sink/circular_buffer.cpp +++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp | |||
| @@ -3,14 +3,14 @@ | |||
| 3 | 3 | ||
| 4 | #include <vector> | 4 | #include <vector> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/sink/circular_buffer.h" | 7 | #include "audio_core/renderer/command/sink/circular_buffer.h" |
| 8 | #include "core/memory.h" | 8 | #include "core/memory.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 12 | void CircularBufferSinkCommand::Dump( |
| 13 | std::string& string) { | 13 | [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) { |
| 14 | string += fmt::format( | 14 | string += fmt::format( |
| 15 | "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", | 15 | "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", |
| 16 | input_count, size, pos); | 16 | input_count, size, pos); |
| @@ -20,7 +20,7 @@ void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListPro | |||
| 20 | string += "\n"; | 20 | string += "\n"; |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) { | 23 | void CircularBufferSinkCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 24 | constexpr s32 min{std::numeric_limits<s16>::min()}; | 24 | constexpr s32 min{std::numeric_limits<s16>::min()}; |
| 25 | constexpr s32 max{std::numeric_limits<s16>::max()}; | 25 | constexpr s32 max{std::numeric_limits<s16>::max()}; |
| 26 | 26 | ||
| @@ -41,8 +41,8 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces | |||
| 41 | } | 41 | } |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | 44 | bool CircularBufferSinkCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 45 | return true; | 45 | return true; |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | } // namespace AudioCore::AudioRenderer | 48 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h index e7d5be26e..a3234a406 100644 --- a/src/audio_core/renderer/command/sink/circular_buffer.h +++ b/src/audio_core/renderer/command/sink/circular_buffer.h | |||
| @@ -8,11 +8,12 @@ | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | 8 | #include "audio_core/renderer/command/icommand.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::ADSP::AudioRenderer { |
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | 12 | class CommandListProcessor; |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 15 | namespace AudioCore::Renderer { | ||
| 16 | |||
| 16 | /** | 17 | /** |
| 17 | * AudioRenderer command for sinking samples to a circular buffer. | 18 | * AudioRenderer command for sinking samples to a circular buffer. |
| 18 | */ | 19 | */ |
| @@ -23,14 +24,14 @@ struct CircularBufferSinkCommand : ICommand { | |||
| 23 | * @param processor - The CommandListProcessor processing this command. | 24 | * @param processor - The CommandListProcessor processing this command. |
| 24 | * @param string - The string to print into. | 25 | * @param string - The string to print into. |
| 25 | */ | 26 | */ |
| 26 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 27 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 27 | 28 | ||
| 28 | /** | 29 | /** |
| 29 | * Process this command. | 30 | * Process this command. |
| 30 | * | 31 | * |
| 31 | * @param processor - The CommandListProcessor processing this command. | 32 | * @param processor - The CommandListProcessor processing this command. |
| 32 | */ | 33 | */ |
| 33 | void Process(const ADSP::CommandListProcessor& processor) override; | 34 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 34 | 35 | ||
| 35 | /** | 36 | /** |
| 36 | * Verify this command's data is valid. | 37 | * Verify this command's data is valid. |
| @@ -38,7 +39,7 @@ struct CircularBufferSinkCommand : ICommand { | |||
| 38 | * @param processor - The CommandListProcessor processing this command. | 39 | * @param processor - The CommandListProcessor processing this command. |
| 39 | * @return True if the command is valid, otherwise false. | 40 | * @return True if the command is valid, otherwise false. |
| 40 | */ | 41 | */ |
| 41 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 42 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 42 | 43 | ||
| 43 | /// Number of input mix buffers | 44 | /// Number of input mix buffers |
| 44 | u32 input_count; | 45 | u32 input_count; |
| @@ -52,4 +53,4 @@ struct CircularBufferSinkCommand : ICommand { | |||
| 52 | u32 pos; | 53 | u32 pos; |
| 53 | }; | 54 | }; |
| 54 | 55 | ||
| 55 | } // namespace AudioCore::AudioRenderer | 56 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp index 5f74dd7ad..3480ed475 100644 --- a/src/audio_core/renderer/command/sink/device.cpp +++ b/src/audio_core/renderer/command/sink/device.cpp | |||
| @@ -3,13 +3,13 @@ | |||
| 3 | 3 | ||
| 4 | #include <algorithm> | 4 | #include <algorithm> |
| 5 | 5 | ||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" |
| 7 | #include "audio_core/renderer/command/sink/device.h" | 7 | #include "audio_core/renderer/command/sink/device.h" |
| 8 | #include "audio_core/sink/sink.h" | 8 | #include "audio_core/sink/sink.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | 12 | void DeviceSinkCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, |
| 13 | std::string& string) { | 13 | std::string& string) { |
| 14 | string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", | 14 | string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", |
| 15 | std::string_view(name), session_id, input_count); | 15 | std::string_view(name), session_id, input_count); |
| @@ -19,7 +19,7 @@ void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& | |||
| 19 | string += "\n"; | 19 | string += "\n"; |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { | 22 | void DeviceSinkCommand::Process(const AudioRenderer::CommandListProcessor& processor) { |
| 23 | constexpr s32 min = std::numeric_limits<s16>::min(); | 23 | constexpr s32 min = std::numeric_limits<s16>::min(); |
| 24 | constexpr s32 max = std::numeric_limits<s16>::max(); | 24 | constexpr s32 max = std::numeric_limits<s16>::max(); |
| 25 | 25 | ||
| @@ -51,8 +51,8 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 51 | } | 51 | } |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | 54 | bool DeviceSinkCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { |
| 55 | return true; | 55 | return true; |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | } // namespace AudioCore::AudioRenderer | 58 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h index 1099bcf8c..385b51ecc 100644 --- a/src/audio_core/renderer/command/sink/device.h +++ b/src/audio_core/renderer/command/sink/device.h | |||
| @@ -10,11 +10,12 @@ | |||
| 10 | #include "audio_core/renderer/command/icommand.h" | 10 | #include "audio_core/renderer/command/icommand.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::ADSP::AudioRenderer { |
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | 14 | class CommandListProcessor; |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 17 | namespace AudioCore::Renderer { | ||
| 18 | |||
| 18 | /** | 19 | /** |
| 19 | * AudioRenderer command for sinking samples to an output device. | 20 | * AudioRenderer command for sinking samples to an output device. |
| 20 | */ | 21 | */ |
| @@ -25,14 +26,14 @@ struct DeviceSinkCommand : ICommand { | |||
| 25 | * @param processor - The CommandListProcessor processing this command. | 26 | * @param processor - The CommandListProcessor processing this command. |
| 26 | * @param string - The string to print into. | 27 | * @param string - The string to print into. |
| 27 | */ | 28 | */ |
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | 29 | void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override; |
| 29 | 30 | ||
| 30 | /** | 31 | /** |
| 31 | * Process this command. | 32 | * Process this command. |
| 32 | * | 33 | * |
| 33 | * @param processor - The CommandListProcessor processing this command. | 34 | * @param processor - The CommandListProcessor processing this command. |
| 34 | */ | 35 | */ |
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | 36 | void Process(const AudioRenderer::CommandListProcessor& processor) override; |
| 36 | 37 | ||
| 37 | /** | 38 | /** |
| 38 | * Verify this command's data is valid. | 39 | * Verify this command's data is valid. |
| @@ -40,7 +41,7 @@ struct DeviceSinkCommand : ICommand { | |||
| 40 | * @param processor - The CommandListProcessor processing this command. | 41 | * @param processor - The CommandListProcessor processing this command. |
| 41 | * @return True if the command is valid, otherwise false. | 42 | * @return True if the command is valid, otherwise false. |
| 42 | */ | 43 | */ |
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | 44 | bool Verify(const AudioRenderer::CommandListProcessor& processor) override; |
| 44 | 45 | ||
| 45 | /// Device name | 46 | /// Device name |
| 46 | char name[0x100]; | 47 | char name[0x100]; |
| @@ -54,4 +55,4 @@ struct DeviceSinkCommand : ICommand { | |||
| 54 | std::array<s16, MaxChannels> inputs; | 55 | std::array<s16, MaxChannels> inputs; |
| 55 | }; | 56 | }; |
| 56 | 57 | ||
| 57 | } // namespace AudioCore::AudioRenderer | 58 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp index 51e780ef1..1c1411eff 100644 --- a/src/audio_core/renderer/effect/aux_.cpp +++ b/src/audio_core/renderer/effect/aux_.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/aux_.h" | 4 | #include "audio_core/renderer/effect/aux_.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | 8 | void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, |
| 9 | const PoolMapper& pool_mapper) { | 9 | const PoolMapper& pool_mapper) { |
| @@ -90,4 +90,4 @@ CpuAddr AuxInfo::GetWorkbuffer(s32 index) { | |||
| 90 | return workbuffers[index].GetReference(true); | 90 | return workbuffers[index].GetReference(true); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | } // namespace AudioCore::AudioRenderer | 93 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h index 4d3d9e3d9..c5b3058da 100644 --- a/src/audio_core/renderer/effect/aux_.h +++ b/src/audio_core/renderer/effect/aux_.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | 9 | #include "audio_core/renderer/effect/effect_info_base.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * Auxiliary Buffer used for Aux commands. | 14 | * Auxiliary Buffer used for Aux commands. |
| 15 | * Send and return buffers are available (names from the game's perspective). | 15 | * Send and return buffers are available (names from the game's perspective). |
| @@ -120,4 +120,4 @@ public: | |||
| 120 | CpuAddr GetWorkbuffer(s32 index) override; | 120 | CpuAddr GetWorkbuffer(s32 index) override; |
| 121 | }; | 121 | }; |
| 122 | 122 | ||
| 123 | } // namespace AudioCore::AudioRenderer | 123 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp index a1efb3231..08161d840 100644 --- a/src/audio_core/renderer/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/effect/biquad_filter.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/biquad_filter.h" | 4 | #include "audio_core/renderer/effect/biquad_filter.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, | 8 | void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, |
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { |
| @@ -49,4 +49,4 @@ void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} | |||
| 49 | void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state, | 49 | void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state, |
| 50 | EffectResultState& dsp_state) {} | 50 | EffectResultState& dsp_state) {} |
| 51 | 51 | ||
| 52 | } // namespace AudioCore::AudioRenderer | 52 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h index f53fd5bab..5a22899ab 100644 --- a/src/audio_core/renderer/effect/biquad_filter.h +++ b/src/audio_core/renderer/effect/biquad_filter.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | 9 | #include "audio_core/renderer/effect/effect_info_base.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | 13 | ||
| 14 | class BiquadFilterInfo : public EffectInfoBase { | 14 | class BiquadFilterInfo : public EffectInfoBase { |
| 15 | public: | 15 | public: |
| @@ -76,4 +76,4 @@ public: | |||
| 76 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | 76 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; |
| 77 | }; | 77 | }; |
| 78 | 78 | ||
| 79 | } // namespace AudioCore::AudioRenderer | 79 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp index 9c8877f01..826e246ec 100644 --- a/src/audio_core/renderer/effect/buffer_mixer.cpp +++ b/src/audio_core/renderer/effect/buffer_mixer.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/buffer_mixer.h" | 4 | #include "audio_core/renderer/effect/buffer_mixer.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, | 8 | void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, |
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { |
| @@ -46,4 +46,4 @@ void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {} | |||
| 46 | void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state, | 46 | void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state, |
| 47 | EffectResultState& dsp_state) {} | 47 | EffectResultState& dsp_state) {} |
| 48 | 48 | ||
| 49 | } // namespace AudioCore::AudioRenderer | 49 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h index 23eed4a8b..0c01ef38d 100644 --- a/src/audio_core/renderer/effect/buffer_mixer.h +++ b/src/audio_core/renderer/effect/buffer_mixer.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | 9 | #include "audio_core/renderer/effect/effect_info_base.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | 13 | ||
| 14 | class BufferMixerInfo : public EffectInfoBase { | 14 | class BufferMixerInfo : public EffectInfoBase { |
| 15 | public: | 15 | public: |
| @@ -72,4 +72,4 @@ public: | |||
| 72 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | 72 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; |
| 73 | }; | 73 | }; |
| 74 | 74 | ||
| 75 | } // namespace AudioCore::AudioRenderer | 75 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp index 3f038efdb..dfa062a59 100644 --- a/src/audio_core/renderer/effect/capture.cpp +++ b/src/audio_core/renderer/effect/capture.cpp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #include "audio_core/renderer/effect/aux_.h" | 4 | #include "audio_core/renderer/effect/aux_.h" |
| 5 | #include "audio_core/renderer/effect/capture.h" | 5 | #include "audio_core/renderer/effect/capture.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | 9 | void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, |
| 10 | const PoolMapper& pool_mapper) { | 10 | const PoolMapper& pool_mapper) { |
| @@ -79,4 +79,4 @@ CpuAddr CaptureInfo::GetWorkbuffer(s32 index) { | |||
| 79 | return workbuffers[index].GetReference(true); | 79 | return workbuffers[index].GetReference(true); |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | } // namespace AudioCore::AudioRenderer | 82 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h index 6fbed8e6b..cbe71e22a 100644 --- a/src/audio_core/renderer/effect/capture.h +++ b/src/audio_core/renderer/effect/capture.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | 9 | #include "audio_core/renderer/effect/effect_info_base.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | 13 | ||
| 14 | class CaptureInfo : public EffectInfoBase { | 14 | class CaptureInfo : public EffectInfoBase { |
| 15 | public: | 15 | public: |
| @@ -62,4 +62,4 @@ public: | |||
| 62 | CpuAddr GetWorkbuffer(s32 index) override; | 62 | CpuAddr GetWorkbuffer(s32 index) override; |
| 63 | }; | 63 | }; |
| 64 | 64 | ||
| 65 | } // namespace AudioCore::AudioRenderer | 65 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp index 220ae02f9..fea0aefcf 100644 --- a/src/audio_core/renderer/effect/compressor.cpp +++ b/src/audio_core/renderer/effect/compressor.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/compressor.h" | 4 | #include "audio_core/renderer/effect/compressor.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, | 8 | void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, |
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} | 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} |
| @@ -37,4 +37,4 @@ CpuAddr CompressorInfo::GetWorkbuffer(s32 index) { | |||
| 37 | return GetSingleBuffer(index); | 37 | return GetSingleBuffer(index); |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | } // namespace AudioCore::AudioRenderer | 40 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h index 019a5ae58..cda55c284 100644 --- a/src/audio_core/renderer/effect/compressor.h +++ b/src/audio_core/renderer/effect/compressor.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "common/fixed_point.h" | 11 | #include "common/fixed_point.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | 14 | ||
| 15 | class CompressorInfo : public EffectInfoBase { | 15 | class CompressorInfo : public EffectInfoBase { |
| 16 | public: | 16 | public: |
| @@ -103,4 +103,4 @@ public: | |||
| 103 | CpuAddr GetWorkbuffer(s32 index) override; | 103 | CpuAddr GetWorkbuffer(s32 index) override; |
| 104 | }; | 104 | }; |
| 105 | 105 | ||
| 106 | } // namespace AudioCore::AudioRenderer | 106 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp index d9853efd9..e038d4498 100644 --- a/src/audio_core/renderer/effect/delay.cpp +++ b/src/audio_core/renderer/effect/delay.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/delay.h" | 4 | #include "audio_core/renderer/effect/delay.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | 8 | void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, |
| 9 | const PoolMapper& pool_mapper) { | 9 | const PoolMapper& pool_mapper) { |
| @@ -90,4 +90,4 @@ CpuAddr DelayInfo::GetWorkbuffer(s32 index) { | |||
| 90 | return GetSingleBuffer(index); | 90 | return GetSingleBuffer(index); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | } // namespace AudioCore::AudioRenderer | 93 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h index accc42a06..47417fbc6 100644 --- a/src/audio_core/renderer/effect/delay.h +++ b/src/audio_core/renderer/effect/delay.h | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/fixed_point.h" | 12 | #include "common/fixed_point.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::Renderer { |
| 15 | 15 | ||
| 16 | class DelayInfo : public EffectInfoBase { | 16 | class DelayInfo : public EffectInfoBase { |
| 17 | public: | 17 | public: |
| @@ -132,4 +132,4 @@ public: | |||
| 132 | CpuAddr GetWorkbuffer(s32 index) override; | 132 | CpuAddr GetWorkbuffer(s32 index) override; |
| 133 | }; | 133 | }; |
| 134 | 134 | ||
| 135 | } // namespace AudioCore::AudioRenderer | 135 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp index 74c7801c9..00f6d7822 100644 --- a/src/audio_core/renderer/effect/effect_context.cpp +++ b/src/audio_core/renderer/effect/effect_context.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/effect_context.h" | 4 | #include "audio_core/renderer/effect/effect_context.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, | 8 | void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, |
| 9 | std::span<EffectResultState> result_states_cpu_, | 9 | std::span<EffectResultState> result_states_cpu_, |
| @@ -38,4 +38,4 @@ void EffectContext::UpdateStateByDspShared() { | |||
| 38 | } | 38 | } |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | } // namespace AudioCore::AudioRenderer | 41 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h index 8f6d6e7d8..8364c5521 100644 --- a/src/audio_core/renderer/effect/effect_context.h +++ b/src/audio_core/renderer/effect/effect_context.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/effect/effect_result_state.h" | 9 | #include "audio_core/renderer/effect/effect_result_state.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | 13 | ||
| 14 | class EffectContext { | 14 | class EffectContext { |
| 15 | public: | 15 | public: |
| @@ -72,4 +72,4 @@ private: | |||
| 72 | size_t dsp_state_count{}; | 72 | size_t dsp_state_count{}; |
| 73 | }; | 73 | }; |
| 74 | 74 | ||
| 75 | } // namespace AudioCore::AudioRenderer | 75 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h index dbdccf278..b49503409 100644 --- a/src/audio_core/renderer/effect/effect_info_base.h +++ b/src/audio_core/renderer/effect/effect_info_base.h | |||
| @@ -12,7 +12,7 @@ | |||
| 12 | #include "audio_core/renderer/memory/pool_mapper.h" | 12 | #include "audio_core/renderer/memory/pool_mapper.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | 14 | ||
| 15 | namespace AudioCore::AudioRenderer { | 15 | namespace AudioCore::Renderer { |
| 16 | /** | 16 | /** |
| 17 | * Base of all effects. Holds various data and functions used for all derived effects. | 17 | * Base of all effects. Holds various data and functions used for all derived effects. |
| 18 | * Should not be used directly. | 18 | * Should not be used directly. |
| @@ -432,4 +432,4 @@ protected: | |||
| 432 | std::array<u8, sizeof(State)> state{}; | 432 | std::array<u8, sizeof(State)> state{}; |
| 433 | }; | 433 | }; |
| 434 | 434 | ||
| 435 | } // namespace AudioCore::AudioRenderer | 435 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h index 1ea67e334..c9e3b4b78 100644 --- a/src/audio_core/renderer/effect/effect_reset.h +++ b/src/audio_core/renderer/effect/effect_reset.h | |||
| @@ -14,7 +14,7 @@ | |||
| 14 | #include "audio_core/renderer/effect/reverb.h" | 14 | #include "audio_core/renderer/effect/reverb.h" |
| 15 | #include "common/common_types.h" | 15 | #include "common/common_types.h" |
| 16 | 16 | ||
| 17 | namespace AudioCore::AudioRenderer { | 17 | namespace AudioCore::Renderer { |
| 18 | /** | 18 | /** |
| 19 | * Reset an effect, and create a new one of the given type. | 19 | * Reset an effect, and create a new one of the given type. |
| 20 | * | 20 | * |
| @@ -68,4 +68,4 @@ static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) | |||
| 68 | } | 68 | } |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | } // namespace AudioCore::AudioRenderer | 71 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h index ae096ad69..f4d4b6086 100644 --- a/src/audio_core/renderer/effect/effect_result_state.h +++ b/src/audio_core/renderer/effect/effect_result_state.h | |||
| @@ -7,10 +7,10 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | struct EffectResultState { | 12 | struct EffectResultState { |
| 13 | std::array<u8, 0x80> state; | 13 | std::array<u8, 0x80> state; |
| 14 | }; | 14 | }; |
| 15 | 15 | ||
| 16 | } // namespace AudioCore::AudioRenderer | 16 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp index 960b29cfc..a3c324c1e 100644 --- a/src/audio_core/renderer/effect/i3dl2.cpp +++ b/src/audio_core/renderer/effect/i3dl2.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/i3dl2.h" | 4 | #include "audio_core/renderer/effect/i3dl2.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, | 8 | void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, |
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { |
| @@ -91,4 +91,4 @@ CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) { | |||
| 91 | return GetSingleBuffer(index); | 91 | return GetSingleBuffer(index); |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | } // namespace AudioCore::AudioRenderer | 94 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h index 6e3ffd1d4..e0432b4ae 100644 --- a/src/audio_core/renderer/effect/i3dl2.h +++ b/src/audio_core/renderer/effect/i3dl2.h | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/fixed_point.h" | 12 | #include "common/fixed_point.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::Renderer { |
| 15 | 15 | ||
| 16 | class I3dl2ReverbInfo : public EffectInfoBase { | 16 | class I3dl2ReverbInfo : public EffectInfoBase { |
| 17 | public: | 17 | public: |
| @@ -198,4 +198,4 @@ public: | |||
| 198 | CpuAddr GetWorkbuffer(s32 index) override; | 198 | CpuAddr GetWorkbuffer(s32 index) override; |
| 199 | }; | 199 | }; |
| 200 | 200 | ||
| 201 | } // namespace AudioCore::AudioRenderer | 201 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp index 1635a952d..9c8ea3c49 100644 --- a/src/audio_core/renderer/effect/light_limiter.cpp +++ b/src/audio_core/renderer/effect/light_limiter.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/light_limiter.h" | 4 | #include "audio_core/renderer/effect/light_limiter.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, | 8 | void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, |
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { |
| @@ -78,4 +78,4 @@ CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) { | |||
| 78 | return GetSingleBuffer(index); | 78 | return GetSingleBuffer(index); |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | } // namespace AudioCore::AudioRenderer | 81 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h index 338d67bbc..7f2ede405 100644 --- a/src/audio_core/renderer/effect/light_limiter.h +++ b/src/audio_core/renderer/effect/light_limiter.h | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/fixed_point.h" | 12 | #include "common/fixed_point.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::Renderer { |
| 15 | 15 | ||
| 16 | class LightLimiterInfo : public EffectInfoBase { | 16 | class LightLimiterInfo : public EffectInfoBase { |
| 17 | public: | 17 | public: |
| @@ -135,4 +135,4 @@ public: | |||
| 135 | CpuAddr GetWorkbuffer(s32 index) override; | 135 | CpuAddr GetWorkbuffer(s32 index) override; |
| 136 | }; | 136 | }; |
| 137 | 137 | ||
| 138 | } // namespace AudioCore::AudioRenderer | 138 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp index 2d32383d0..4da72469a 100644 --- a/src/audio_core/renderer/effect/reverb.cpp +++ b/src/audio_core/renderer/effect/reverb.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/effect/reverb.h" | 4 | #include "audio_core/renderer/effect/reverb.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | 8 | void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, |
| 9 | const PoolMapper& pool_mapper) { | 9 | const PoolMapper& pool_mapper) { |
| @@ -90,4 +90,4 @@ CpuAddr ReverbInfo::GetWorkbuffer(s32 index) { | |||
| 90 | return GetSingleBuffer(index); | 90 | return GetSingleBuffer(index); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | } // namespace AudioCore::AudioRenderer | 93 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h index 6cc345ef6..52a048da6 100644 --- a/src/audio_core/renderer/effect/reverb.h +++ b/src/audio_core/renderer/effect/reverb.h | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/fixed_point.h" | 12 | #include "common/fixed_point.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::Renderer { |
| 15 | 15 | ||
| 16 | class ReverbInfo : public EffectInfoBase { | 16 | class ReverbInfo : public EffectInfoBase { |
| 17 | public: | 17 | public: |
| @@ -187,4 +187,4 @@ public: | |||
| 187 | CpuAddr GetWorkbuffer(s32 index) override; | 187 | CpuAddr GetWorkbuffer(s32 index) override; |
| 188 | }; | 188 | }; |
| 189 | 189 | ||
| 190 | } // namespace AudioCore::AudioRenderer | 190 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h index bb5c930e1..c81ef1b8a 100644 --- a/src/audio_core/renderer/memory/address_info.h +++ b/src/audio_core/renderer/memory/address_info.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/memory/memory_pool_info.h" | 6 | #include "audio_core/renderer/memory/memory_pool_info.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | /** | 11 | /** |
| 12 | * Represents a region of mapped or unmapped memory. | 12 | * Represents a region of mapped or unmapped memory. |
| @@ -121,4 +121,4 @@ private: | |||
| 121 | CpuAddr dsp_address; | 121 | CpuAddr dsp_address; |
| 122 | }; | 122 | }; |
| 123 | 123 | ||
| 124 | } // namespace AudioCore::AudioRenderer | 124 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp index 9b7824af1..03b44d5f3 100644 --- a/src/audio_core/renderer/memory/memory_pool_info.cpp +++ b/src/audio_core/renderer/memory/memory_pool_info.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/memory/memory_pool_info.h" | 4 | #include "audio_core/renderer/memory/memory_pool_info.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | CpuAddr MemoryPoolInfo::GetCpuAddress() const { | 8 | CpuAddr MemoryPoolInfo::GetCpuAddress() const { |
| 9 | return cpu_address; | 9 | return cpu_address; |
| @@ -58,4 +58,4 @@ bool MemoryPoolInfo::IsUsed() const { | |||
| 58 | return in_use; | 58 | return in_use; |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | } // namespace AudioCore::AudioRenderer | 61 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h index 80c571bc1..2f9c85184 100644 --- a/src/audio_core/renderer/memory/memory_pool_info.h +++ b/src/audio_core/renderer/memory/memory_pool_info.h | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | #include "audio_core/common/common.h" | 8 | #include "audio_core/common/common.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | /** | 12 | /** |
| 13 | * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). | 13 | * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). |
| 14 | */ | 14 | */ |
| @@ -167,4 +167,4 @@ private: | |||
| 167 | bool in_use{}; | 167 | bool in_use{}; |
| 168 | }; | 168 | }; |
| 169 | 169 | ||
| 170 | } // namespace AudioCore::AudioRenderer | 170 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp index 7fd2b5f47..999bb746b 100644 --- a/src/audio_core/renderer/memory/pool_mapper.cpp +++ b/src/audio_core/renderer/memory/pool_mapper.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "core/hle/kernel/k_process.h" | 6 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/svc.h" | 7 | #include "core/hle/kernel/svc.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) | 11 | PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) |
| 12 | : process_handle{process_handle_}, force_map{force_map_} {} | 12 | : process_handle{process_handle_}, force_map{force_map_} {} |
| @@ -240,4 +240,4 @@ bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, | |||
| 240 | } | 240 | } |
| 241 | } | 241 | } |
| 242 | 242 | ||
| 243 | } // namespace AudioCore::AudioRenderer | 243 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h index 9a691da7a..95ae5d8ea 100644 --- a/src/audio_core/renderer/memory/pool_mapper.h +++ b/src/audio_core/renderer/memory/pool_mapper.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "core/hle/service/audio/errors.h" | 11 | #include "core/hle/service/audio/errors.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | class AddressInfo; | 14 | class AddressInfo; |
| 15 | 15 | ||
| 16 | /** | 16 | /** |
| @@ -176,4 +176,4 @@ private: | |||
| 176 | bool force_map; | 176 | bool force_map; |
| 177 | }; | 177 | }; |
| 178 | 178 | ||
| 179 | } // namespace AudioCore::AudioRenderer | 179 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp index 3a18ae7c2..c712610bb 100644 --- a/src/audio_core/renderer/mix/mix_context.cpp +++ b/src/audio_core/renderer/mix/mix_context.cpp | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include "audio_core/renderer/splitter/splitter_context.h" | 7 | #include "audio_core/renderer/splitter/splitter_context.h" |
| 8 | #include "common/polyfill_ranges.h" | 8 | #include "common/polyfill_ranges.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_, | 12 | void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_, |
| 13 | const u32 count_, std::span<s32> effect_process_order_buffer_, | 13 | const u32 count_, std::span<s32> effect_process_order_buffer_, |
| @@ -139,4 +139,4 @@ EdgeMatrix& MixContext::GetEdgeMatrix() { | |||
| 139 | return edge_matrix; | 139 | return edge_matrix; |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | } // namespace AudioCore::AudioRenderer | 142 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h index bcd9637da..ce19ec8d6 100644 --- a/src/audio_core/renderer/mix/mix_context.h +++ b/src/audio_core/renderer/mix/mix_context.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "audio_core/renderer/nodes/node_states.h" | 10 | #include "audio_core/renderer/nodes/node_states.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | class SplitterContext; | 14 | class SplitterContext; |
| 15 | 15 | ||
| 16 | /* | 16 | /* |
| @@ -121,4 +121,4 @@ private: | |||
| 121 | EdgeMatrix edge_matrix{}; | 121 | EdgeMatrix edge_matrix{}; |
| 122 | }; | 122 | }; |
| 123 | 123 | ||
| 124 | } // namespace AudioCore::AudioRenderer | 124 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp index cc18e57ee..5e44bde18 100644 --- a/src/audio_core/renderer/mix/mix_info.cpp +++ b/src/audio_core/renderer/mix/mix_info.cpp | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include "audio_core/renderer/nodes/edge_matrix.h" | 7 | #include "audio_core/renderer/nodes/edge_matrix.h" |
| 8 | #include "audio_core/renderer/splitter/splitter_context.h" | 8 | #include "audio_core/renderer/splitter/splitter_context.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) | 12 | MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) |
| 13 | : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, | 13 | : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, |
| @@ -117,4 +117,4 @@ bool MixInfo::HasAnyConnection() const { | |||
| 117 | return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; | 117 | return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; |
| 118 | } | 118 | } |
| 119 | 119 | ||
| 120 | } // namespace AudioCore::AudioRenderer | 120 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h index b5fa4c0c7..7005daa4f 100644 --- a/src/audio_core/renderer/mix/mix_info.h +++ b/src/audio_core/renderer/mix/mix_info.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/common/common.h" | 9 | #include "audio_core/common/common.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | class EdgeMatrix; | 13 | class EdgeMatrix; |
| 14 | class SplitterContext; | 14 | class SplitterContext; |
| 15 | class EffectContext; | 15 | class EffectContext; |
| @@ -121,4 +121,4 @@ public: | |||
| 121 | const bool long_size_pre_delay_supported; | 121 | const bool long_size_pre_delay_supported; |
| 122 | }; | 122 | }; |
| 123 | 123 | ||
| 124 | } // namespace AudioCore::AudioRenderer | 124 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h index b0d53cd51..d8a2d09d0 100644 --- a/src/audio_core/renderer/nodes/bit_array.h +++ b/src/audio_core/renderer/nodes/bit_array.h | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | /** | 11 | /** |
| 12 | * Represents an array of bits used for nodes and edges for the mixing graph. | 12 | * Represents an array of bits used for nodes and edges for the mixing graph. |
| 13 | */ | 13 | */ |
| @@ -22,4 +22,4 @@ struct BitArray { | |||
| 22 | u32 size{}; | 22 | u32 size{}; |
| 23 | }; | 23 | }; |
| 24 | 24 | ||
| 25 | } // namespace AudioCore::AudioRenderer | 25 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp index 5573f33b9..c28773b22 100644 --- a/src/audio_core/renderer/nodes/edge_matrix.cpp +++ b/src/audio_core/renderer/nodes/edge_matrix.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/nodes/edge_matrix.h" | 4 | #include "audio_core/renderer/nodes/edge_matrix.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer, | 8 | void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer, |
| 9 | [[maybe_unused]] const u64 node_buffer_size, const u32 count_) { | 9 | [[maybe_unused]] const u64 node_buffer_size, const u32 count_) { |
| @@ -35,4 +35,4 @@ u32 EdgeMatrix::GetNodeCount() const { | |||
| 35 | return count; | 35 | return count; |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | } // namespace AudioCore::AudioRenderer | 38 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h index 27a20e43e..0271c23b1 100644 --- a/src/audio_core/renderer/nodes/edge_matrix.h +++ b/src/audio_core/renderer/nodes/edge_matrix.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "common/alignment.h" | 9 | #include "common/alignment.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * An edge matrix, holding the connections for each node to every other node in the graph. | 14 | * An edge matrix, holding the connections for each node to every other node in the graph. |
| 15 | */ | 15 | */ |
| @@ -79,4 +79,4 @@ private: | |||
| 79 | u32 count; | 79 | u32 count; |
| 80 | }; | 80 | }; |
| 81 | 81 | ||
| 82 | } // namespace AudioCore::AudioRenderer | 82 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp index b7a44a54c..028a58041 100644 --- a/src/audio_core/renderer/nodes/node_states.cpp +++ b/src/audio_core/renderer/nodes/node_states.cpp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #include "audio_core/renderer/nodes/node_states.h" | 4 | #include "audio_core/renderer/nodes/node_states.h" |
| 5 | #include "common/logging/log.h" | 5 | #include "common/logging/log.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size, | 9 | void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size, |
| 10 | const u32 count) { | 10 | const u32 count) { |
| @@ -138,4 +138,4 @@ std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls( | |||
| 138 | return {results.rbegin(), result_pos}; | 138 | return {results.rbegin(), result_pos}; |
| 139 | } | 139 | } |
| 140 | 140 | ||
| 141 | } // namespace AudioCore::AudioRenderer | 141 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h index e768cd4b5..991a82841 100644 --- a/src/audio_core/renderer/nodes/node_states.h +++ b/src/audio_core/renderer/nodes/node_states.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "common/alignment.h" | 10 | #include "common/alignment.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | /** | 14 | /** |
| 15 | * Graph utility functions for sorting and getting results from the DAG. | 15 | * Graph utility functions for sorting and getting results from the DAG. |
| 16 | */ | 16 | */ |
| @@ -192,4 +192,4 @@ private: | |||
| 192 | Stack stack{}; | 192 | Stack stack{}; |
| 193 | }; | 193 | }; |
| 194 | 194 | ||
| 195 | } // namespace AudioCore::AudioRenderer | 195 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp index f6405937f..ef8b47cee 100644 --- a/src/audio_core/renderer/performance/detail_aspect.cpp +++ b/src/audio_core/renderer/performance/detail_aspect.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include "audio_core/renderer/command/command_generator.h" | 5 | #include "audio_core/renderer/command/command_generator.h" |
| 6 | #include "audio_core/renderer/performance/detail_aspect.h" | 6 | #include "audio_core/renderer/performance/detail_aspect.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | DetailAspect::DetailAspect(CommandGenerator& command_generator_, | 10 | DetailAspect::DetailAspect(CommandGenerator& command_generator_, |
| 11 | const PerformanceEntryType entry_type, const s32 node_id_, | 11 | const PerformanceEntryType entry_type, const s32 node_id_, |
| @@ -22,4 +22,4 @@ DetailAspect::DetailAspect(CommandGenerator& command_generator_, | |||
| 22 | } | 22 | } |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | } // namespace AudioCore::AudioRenderer | 25 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h index 736c331b9..0bd7f80c8 100644 --- a/src/audio_core/renderer/performance/detail_aspect.h +++ b/src/audio_core/renderer/performance/detail_aspect.h | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include "audio_core/renderer/performance/performance_manager.h" | 7 | #include "audio_core/renderer/performance/performance_manager.h" |
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | class CommandGenerator; | 11 | class CommandGenerator; |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| @@ -29,4 +29,4 @@ public: | |||
| 29 | s32 node_id; | 29 | s32 node_id; |
| 30 | }; | 30 | }; |
| 31 | 31 | ||
| 32 | } // namespace AudioCore::AudioRenderer | 32 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp index dd4165803..c9241a639 100644 --- a/src/audio_core/renderer/performance/entry_aspect.cpp +++ b/src/audio_core/renderer/performance/entry_aspect.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include "audio_core/renderer/command/command_generator.h" | 5 | #include "audio_core/renderer/command/command_generator.h" |
| 6 | #include "audio_core/renderer/performance/entry_aspect.h" | 6 | #include "audio_core/renderer/performance/entry_aspect.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type, | 10 | EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type, |
| 11 | const s32 node_id_) | 11 | const s32 node_id_) |
| @@ -20,4 +20,4 @@ EntryAspect::EntryAspect(CommandGenerator& command_generator_, const Performance | |||
| 20 | } | 20 | } |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | } // namespace AudioCore::AudioRenderer | 23 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h index 14c9e3baf..f99287d68 100644 --- a/src/audio_core/renderer/performance/entry_aspect.h +++ b/src/audio_core/renderer/performance/entry_aspect.h | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include "audio_core/renderer/performance/performance_manager.h" | 7 | #include "audio_core/renderer/performance/performance_manager.h" |
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | class CommandGenerator; | 11 | class CommandGenerator; |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| @@ -28,4 +28,4 @@ public: | |||
| 28 | s32 node_id; | 28 | s32 node_id; |
| 29 | }; | 29 | }; |
| 30 | 30 | ||
| 31 | } // namespace AudioCore::AudioRenderer | 31 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h index f603b9026..2b0cf9422 100644 --- a/src/audio_core/renderer/performance/performance_detail.h +++ b/src/audio_core/renderer/performance/performance_detail.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/performance/performance_entry.h" | 6 | #include "audio_core/renderer/performance/performance_entry.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | enum class PerformanceDetailType : u8 { | 11 | enum class PerformanceDetailType : u8 { |
| 12 | Invalid, | 12 | Invalid, |
| @@ -47,4 +47,4 @@ struct PerformanceDetailVersion2 { | |||
| 47 | static_assert(sizeof(PerformanceDetailVersion2) == 0x18, | 47 | static_assert(sizeof(PerformanceDetailVersion2) == 0x18, |
| 48 | "PerformanceDetailVersion2 has the wrong size!"); | 48 | "PerformanceDetailVersion2 has the wrong size!"); |
| 49 | 49 | ||
| 50 | } // namespace AudioCore::AudioRenderer | 50 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h index d6b1158db..dbd6053a5 100644 --- a/src/audio_core/renderer/performance/performance_entry.h +++ b/src/audio_core/renderer/performance/performance_entry.h | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include "common/common_types.h" | 6 | #include "common/common_types.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | enum class PerformanceEntryType : u8 { | 10 | enum class PerformanceEntryType : u8 { |
| 11 | Invalid, | 11 | Invalid, |
| @@ -34,4 +34,4 @@ struct PerformanceEntryVersion2 { | |||
| 34 | static_assert(sizeof(PerformanceEntryVersion2) == 0x18, | 34 | static_assert(sizeof(PerformanceEntryVersion2) == 0x18, |
| 35 | "PerformanceEntryVersion2 has the wrong size!"); | 35 | "PerformanceEntryVersion2 has the wrong size!"); |
| 36 | 36 | ||
| 37 | } // namespace AudioCore::AudioRenderer | 37 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h index e381d765c..51eee975f 100644 --- a/src/audio_core/renderer/performance/performance_entry_addresses.h +++ b/src/audio_core/renderer/performance/performance_entry_addresses.h | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include "audio_core/common/common.h" | 6 | #include "audio_core/common/common.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | struct PerformanceEntryAddresses { | 10 | struct PerformanceEntryAddresses { |
| 11 | CpuAddr translated_address; | 11 | CpuAddr translated_address; |
| @@ -14,4 +14,4 @@ struct PerformanceEntryAddresses { | |||
| 14 | CpuAddr entry_processed_time_offset; | 14 | CpuAddr entry_processed_time_offset; |
| 15 | }; | 15 | }; |
| 16 | 16 | ||
| 17 | } // namespace AudioCore::AudioRenderer | 17 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h index b1848284e..24e4989f8 100644 --- a/src/audio_core/renderer/performance/performance_frame_header.h +++ b/src/audio_core/renderer/performance/performance_frame_header.h | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include "common/common_types.h" | 6 | #include "common/common_types.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | struct PerformanceFrameHeaderVersion1 { | 10 | struct PerformanceFrameHeaderVersion1 { |
| 11 | /* 0x00 */ u32 magic; // "PERF" | 11 | /* 0x00 */ u32 magic; // "PERF" |
| @@ -33,4 +33,4 @@ struct PerformanceFrameHeaderVersion2 { | |||
| 33 | static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30, | 33 | static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30, |
| 34 | "PerformanceFrameHeaderVersion2 has the wrong size!"); | 34 | "PerformanceFrameHeaderVersion2 has the wrong size!"); |
| 35 | 35 | ||
| 36 | } // namespace AudioCore::AudioRenderer | 36 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp index 8aa0f5ed0..ce736db71 100644 --- a/src/audio_core/renderer/performance/performance_manager.cpp +++ b/src/audio_core/renderer/performance/performance_manager.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/performance/performance_manager.h" | 6 | #include "audio_core/renderer/performance/performance_manager.h" |
| 7 | #include "common/common_funcs.h" | 7 | #include "common/common_funcs.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | void PerformanceManager::CreateImpl(const size_t version) { | 11 | void PerformanceManager::CreateImpl(const size_t version) { |
| 12 | switch (version) { | 12 | switch (version) { |
| @@ -643,4 +643,4 @@ void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeader | |||
| 643 | target_node_id = target_node_id_; | 643 | target_node_id = target_node_id_; |
| 644 | } | 644 | } |
| 645 | 645 | ||
| 646 | } // namespace AudioCore::AudioRenderer | 646 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h index b65caa9b6..ffd0fa1fb 100644 --- a/src/audio_core/renderer/performance/performance_manager.h +++ b/src/audio_core/renderer/performance/performance_manager.h | |||
| @@ -14,7 +14,7 @@ | |||
| 14 | #include "audio_core/renderer/performance/performance_frame_header.h" | 14 | #include "audio_core/renderer/performance/performance_frame_header.h" |
| 15 | #include "common/common_types.h" | 15 | #include "common/common_types.h" |
| 16 | 16 | ||
| 17 | namespace AudioCore::AudioRenderer { | 17 | namespace AudioCore::Renderer { |
| 18 | class BehaviorInfo; | 18 | class BehaviorInfo; |
| 19 | class MemoryPoolInfo; | 19 | class MemoryPoolInfo; |
| 20 | 20 | ||
| @@ -272,4 +272,4 @@ private: | |||
| 272 | PerformanceVersion version{}; | 272 | PerformanceVersion version{}; |
| 273 | }; | 273 | }; |
| 274 | 274 | ||
| 275 | } // namespace AudioCore::AudioRenderer | 275 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp index d91f10402..0ede02b6b 100644 --- a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include "audio_core/renderer/sink/circular_buffer_sink_info.h" | 5 | #include "audio_core/renderer/sink/circular_buffer_sink_info.h" |
| 6 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | 6 | #include "audio_core/renderer/upsampler/upsampler_manager.h" |
| 7 | 7 | ||
| 8 | namespace AudioCore::AudioRenderer { | 8 | namespace AudioCore::Renderer { |
| 9 | 9 | ||
| 10 | CircularBufferSinkInfo::CircularBufferSinkInfo() { | 10 | CircularBufferSinkInfo::CircularBufferSinkInfo() { |
| 11 | state.fill(0); | 11 | state.fill(0); |
| @@ -73,4 +73,4 @@ void CircularBufferSinkInfo::UpdateForCommandGeneration() { | |||
| 73 | } | 73 | } |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | } // namespace AudioCore::AudioRenderer | 76 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h index 3356213ea..d4e61d641 100644 --- a/src/audio_core/renderer/sink/circular_buffer_sink_info.h +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/sink/sink_info_base.h" | 6 | #include "audio_core/renderer/sink/sink_info_base.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Info for a circular buffer sink. | 11 | * Info for a circular buffer sink. |
| 12 | */ | 12 | */ |
| @@ -38,4 +38,4 @@ public: | |||
| 38 | static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase), | 38 | static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase), |
| 39 | "CircularBufferSinkInfo is too large!"); | 39 | "CircularBufferSinkInfo is too large!"); |
| 40 | 40 | ||
| 41 | } // namespace AudioCore::AudioRenderer | 41 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp index b7b3d6f1d..2de05e38e 100644 --- a/src/audio_core/renderer/sink/device_sink_info.cpp +++ b/src/audio_core/renderer/sink/device_sink_info.cpp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #include "audio_core/renderer/sink/device_sink_info.h" | 4 | #include "audio_core/renderer/sink/device_sink_info.h" |
| 5 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | 5 | #include "audio_core/renderer/upsampler/upsampler_manager.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | DeviceSinkInfo::DeviceSinkInfo() { | 9 | DeviceSinkInfo::DeviceSinkInfo() { |
| 10 | state.fill(0); | 10 | state.fill(0); |
| @@ -54,4 +54,4 @@ void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_ | |||
| 54 | 54 | ||
| 55 | void DeviceSinkInfo::UpdateForCommandGeneration() {} | 55 | void DeviceSinkInfo::UpdateForCommandGeneration() {} |
| 56 | 56 | ||
| 57 | } // namespace AudioCore::AudioRenderer | 57 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h index a1c441454..7974ae820 100644 --- a/src/audio_core/renderer/sink/device_sink_info.h +++ b/src/audio_core/renderer/sink/device_sink_info.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/sink/sink_info_base.h" | 6 | #include "audio_core/renderer/sink/sink_info_base.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Info for a device sink. | 11 | * Info for a device sink. |
| 12 | */ | 12 | */ |
| @@ -37,4 +37,4 @@ public: | |||
| 37 | }; | 37 | }; |
| 38 | static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!"); | 38 | static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!"); |
| 39 | 39 | ||
| 40 | } // namespace AudioCore::AudioRenderer | 40 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp index 634bc1cf9..a4f9cac21 100644 --- a/src/audio_core/renderer/sink/sink_context.cpp +++ b/src/audio_core/renderer/sink/sink_context.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/sink/sink_context.h" | 4 | #include "audio_core/renderer/sink/sink_context.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) { | 8 | void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) { |
| 9 | sink_infos = sink_infos_; | 9 | sink_infos = sink_infos_; |
| @@ -18,4 +18,4 @@ u32 SinkContext::GetCount() const { | |||
| 18 | return sink_count; | 18 | return sink_count; |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | } // namespace AudioCore::AudioRenderer | 21 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h index 185572e29..66925b48e 100644 --- a/src/audio_core/renderer/sink/sink_context.h +++ b/src/audio_core/renderer/sink/sink_context.h | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | #include "audio_core/renderer/sink/sink_info_base.h" | 8 | #include "audio_core/renderer/sink/sink_info_base.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | /** | 12 | /** |
| 13 | * Manages output sinks. | 13 | * Manages output sinks. |
| 14 | */ | 14 | */ |
| @@ -44,4 +44,4 @@ private: | |||
| 44 | u32 sink_count{}; | 44 | u32 sink_count{}; |
| 45 | }; | 45 | }; |
| 46 | 46 | ||
| 47 | } // namespace AudioCore::AudioRenderer | 47 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp index 4279beaa0..8a064f15a 100644 --- a/src/audio_core/renderer/sink/sink_info_base.cpp +++ b/src/audio_core/renderer/sink/sink_info_base.cpp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #include "audio_core/renderer/memory/pool_mapper.h" | 4 | #include "audio_core/renderer/memory/pool_mapper.h" |
| 5 | #include "audio_core/renderer/sink/sink_info_base.h" | 5 | #include "audio_core/renderer/sink/sink_info_base.h" |
| 6 | 6 | ||
| 7 | namespace AudioCore::AudioRenderer { | 7 | namespace AudioCore::Renderer { |
| 8 | 8 | ||
| 9 | void SinkInfoBase::CleanUp() { | 9 | void SinkInfoBase::CleanUp() { |
| 10 | type = Type::Invalid; | 10 | type = Type::Invalid; |
| @@ -48,4 +48,4 @@ u8* SinkInfoBase::GetParameter() { | |||
| 48 | return parameter.data(); | 48 | return parameter.data(); |
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | } // namespace AudioCore::AudioRenderer | 51 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h index a1b855f20..e10d1cb38 100644 --- a/src/audio_core/renderer/sink/sink_info_base.h +++ b/src/audio_core/renderer/sink/sink_info_base.h | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/fixed_point.h" | 12 | #include "common/fixed_point.h" |
| 13 | 13 | ||
| 14 | namespace AudioCore::AudioRenderer { | 14 | namespace AudioCore::Renderer { |
| 15 | struct UpsamplerInfo; | 15 | struct UpsamplerInfo; |
| 16 | class PoolMapper; | 16 | class PoolMapper; |
| 17 | 17 | ||
| @@ -174,4 +174,4 @@ protected: | |||
| 174 | parameter{}; | 174 | parameter{}; |
| 175 | }; | 175 | }; |
| 176 | 176 | ||
| 177 | } // namespace AudioCore::AudioRenderer | 177 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp index 7a23ba43f..686150ea6 100644 --- a/src/audio_core/renderer/splitter/splitter_context.cpp +++ b/src/audio_core/renderer/splitter/splitter_context.cpp | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include "audio_core/renderer/splitter/splitter_context.h" | 7 | #include "audio_core/renderer/splitter/splitter_context.h" |
| 8 | #include "common/alignment.h" | 8 | #include "common/alignment.h" |
| 9 | 9 | ||
| 10 | namespace AudioCore::AudioRenderer { | 10 | namespace AudioCore::Renderer { |
| 11 | 11 | ||
| 12 | SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, | 12 | SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, |
| 13 | const s32 destination_id) { | 13 | const s32 destination_id) { |
| @@ -214,4 +214,4 @@ u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior, | |||
| 214 | return size; | 214 | return size; |
| 215 | } | 215 | } |
| 216 | 216 | ||
| 217 | } // namespace AudioCore::AudioRenderer | 217 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h index 1a63db1d3..556e6dcc3 100644 --- a/src/audio_core/renderer/splitter/splitter_context.h +++ b/src/audio_core/renderer/splitter/splitter_context.h | |||
| @@ -13,7 +13,7 @@ namespace AudioCore { | |||
| 13 | struct AudioRendererParameterInternal; | 13 | struct AudioRendererParameterInternal; |
| 14 | class WorkbufferAllocator; | 14 | class WorkbufferAllocator; |
| 15 | 15 | ||
| 16 | namespace AudioRenderer { | 16 | namespace Renderer { |
| 17 | class BehaviorInfo; | 17 | class BehaviorInfo; |
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| @@ -185,5 +185,5 @@ private: | |||
| 185 | bool splitter_bug_fixed{}; | 185 | bool splitter_bug_fixed{}; |
| 186 | }; | 186 | }; |
| 187 | 187 | ||
| 188 | } // namespace AudioRenderer | 188 | } // namespace Renderer |
| 189 | } // namespace AudioCore | 189 | } // namespace AudioCore |
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp index b27d44896..5ec37e48e 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/splitter/splitter_destinations_data.h" | 4 | #include "audio_core/renderer/splitter/splitter_destinations_data.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} | 8 | SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} |
| 9 | 9 | ||
| @@ -84,4 +84,4 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { | |||
| 84 | next = next_; | 84 | next = next_; |
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | } // namespace AudioCore::AudioRenderer | 87 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h index d55ce0ad3..90edfc667 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.h +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/common/common.h" | 9 | #include "audio_core/common/common.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * Represents a mixing node, can be connected to a previous and next destination forming a chain | 14 | * Represents a mixing node, can be connected to a previous and next destination forming a chain |
| 15 | * that a certain mix buffer will pass through to output. | 15 | * that a certain mix buffer will pass through to output. |
| @@ -132,4 +132,4 @@ private: | |||
| 132 | bool need_update{}; | 132 | bool need_update{}; |
| 133 | }; | 133 | }; |
| 134 | 134 | ||
| 135 | } // namespace AudioCore::AudioRenderer | 135 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp index 1aee6720b..beb5b7f19 100644 --- a/src/audio_core/renderer/splitter/splitter_info.cpp +++ b/src/audio_core/renderer/splitter/splitter_info.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/splitter/splitter_info.h" | 4 | #include "audio_core/renderer/splitter/splitter_info.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} | 8 | SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} |
| 9 | 9 | ||
| @@ -76,4 +76,4 @@ void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) { | |||
| 76 | destinations = destinations_; | 76 | destinations = destinations_; |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | } // namespace AudioCore::AudioRenderer | 79 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h index b0ad01fe0..c1e4c2df1 100644 --- a/src/audio_core/renderer/splitter/splitter_info.h +++ b/src/audio_core/renderer/splitter/splitter_info.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/splitter/splitter_destinations_data.h" | 6 | #include "audio_core/renderer/splitter/splitter_destinations_data.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | /** | 10 | /** |
| 11 | * Represents a splitter, wraps multiple output destinations to split an input mix into. | 11 | * Represents a splitter, wraps multiple output destinations to split an input mix into. |
| 12 | */ | 12 | */ |
| @@ -104,4 +104,4 @@ private: | |||
| 104 | u32 channel_count{}; | 104 | u32 channel_count{}; |
| 105 | }; | 105 | }; |
| 106 | 106 | ||
| 107 | } // namespace AudioCore::AudioRenderer | 107 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index a23627472..31f92087c 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp | |||
| @@ -4,12 +4,13 @@ | |||
| 4 | #include <chrono> | 4 | #include <chrono> |
| 5 | #include <span> | 5 | #include <span> |
| 6 | 6 | ||
| 7 | #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" | ||
| 8 | #include "audio_core/adsp/apps/audio_renderer/command_buffer.h" | ||
| 7 | #include "audio_core/audio_core.h" | 9 | #include "audio_core/audio_core.h" |
| 8 | #include "audio_core/common/audio_renderer_parameter.h" | 10 | #include "audio_core/common/audio_renderer_parameter.h" |
| 9 | #include "audio_core/common/common.h" | 11 | #include "audio_core/common/common.h" |
| 10 | #include "audio_core/common/feature_support.h" | 12 | #include "audio_core/common/feature_support.h" |
| 11 | #include "audio_core/common/workbuffer_allocator.h" | 13 | #include "audio_core/common/workbuffer_allocator.h" |
| 12 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 13 | #include "audio_core/renderer/behavior/info_updater.h" | 14 | #include "audio_core/renderer/behavior/info_updater.h" |
| 14 | #include "audio_core/renderer/command/command_buffer.h" | 15 | #include "audio_core/renderer/command/command_buffer.h" |
| 15 | #include "audio_core/renderer/command/command_generator.h" | 16 | #include "audio_core/renderer/command/command_generator.h" |
| @@ -34,7 +35,7 @@ | |||
| 34 | #include "core/hle/kernel/k_transfer_memory.h" | 35 | #include "core/hle/kernel/k_transfer_memory.h" |
| 35 | #include "core/memory.h" | 36 | #include "core/memory.h" |
| 36 | 37 | ||
| 37 | namespace AudioCore::AudioRenderer { | 38 | namespace AudioCore::Renderer { |
| 38 | 39 | ||
| 39 | u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { | 40 | u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { |
| 40 | BehaviorInfo behavior; | 41 | BehaviorInfo behavior; |
| @@ -95,7 +96,8 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { | |||
| 95 | } | 96 | } |
| 96 | 97 | ||
| 97 | System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) | 98 | System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) |
| 98 | : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} | 99 | : core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()}, |
| 100 | adsp_rendered_event{adsp_rendered_event_} {} | ||
| 99 | 101 | ||
| 100 | Result System::Initialize(const AudioRendererParameterInternal& params, | 102 | Result System::Initialize(const AudioRendererParameterInternal& params, |
| 101 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, | 103 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, |
| @@ -443,7 +445,7 @@ void System::Stop() { | |||
| 443 | Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) { | 445 | Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) { |
| 444 | std::scoped_lock l{lock}; | 446 | std::scoped_lock l{lock}; |
| 445 | 447 | ||
| 446 | const auto start_time{core.CoreTiming().GetClockTicks()}; | 448 | const auto start_time{core.CoreTiming().GetGlobalTimeNs().count()}; |
| 447 | std::memset(output.data(), 0, output.size()); | 449 | std::memset(output.data(), 0, output.size()); |
| 448 | 450 | ||
| 449 | InfoUpdater info_updater(input, output, process_handle, behavior); | 451 | InfoUpdater info_updater(input, output, process_handle, behavior); |
| @@ -535,7 +537,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std: | |||
| 535 | adsp_rendered_event->Clear(); | 537 | adsp_rendered_event->Clear(); |
| 536 | num_times_updated++; | 538 | num_times_updated++; |
| 537 | 539 | ||
| 538 | const auto end_time{core.CoreTiming().GetClockTicks()}; | 540 | const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()}; |
| 539 | ticks_spent_updating += end_time - start_time; | 541 | ticks_spent_updating += end_time - start_time; |
| 540 | 542 | ||
| 541 | return ResultSuccess; | 543 | return ResultSuccess; |
| @@ -583,7 +585,7 @@ void System::SendCommandToDsp() { | |||
| 583 | if (initialized) { | 585 | if (initialized) { |
| 584 | if (active) { | 586 | if (active) { |
| 585 | terminate_event.Reset(); | 587 | terminate_event.Reset(); |
| 586 | const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)}; | 588 | const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)}; |
| 587 | u64 command_size{0}; | 589 | u64 command_size{0}; |
| 588 | 590 | ||
| 589 | if (remaining_command_count) { | 591 | if (remaining_command_count) { |
| @@ -607,26 +609,18 @@ void System::SendCommandToDsp() { | |||
| 607 | time_limit_percent = 70.0f; | 609 | time_limit_percent = 70.0f; |
| 608 | } | 610 | } |
| 609 | 611 | ||
| 610 | ADSP::CommandBuffer command_buffer{ | 612 | auto time_limit{ |
| 611 | .buffer{translated_addr}, | 613 | static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * |
| 612 | .size{command_size}, | 614 | (static_cast<f32>(render_time_limit_percent) / 100.0f))}; |
| 613 | .time_limit{ | 615 | audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit, |
| 614 | static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * | 616 | applet_resource_user_id, reset_command_buffers); |
| 615 | (static_cast<f32>(render_time_limit_percent) / 100.0f))}, | ||
| 616 | .remaining_command_count{remaining_command_count}, | ||
| 617 | .reset_buffers{reset_command_buffers}, | ||
| 618 | .applet_resource_user_id{applet_resource_user_id}, | ||
| 619 | .render_time_taken{adsp.GetRenderTimeTaken(session_id)}, | ||
| 620 | }; | ||
| 621 | |||
| 622 | adsp.SendCommandBuffer(session_id, command_buffer); | ||
| 623 | reset_command_buffers = false; | 617 | reset_command_buffers = false; |
| 624 | command_buffer_size = command_size; | 618 | command_buffer_size = command_size; |
| 625 | if (remaining_command_count == 0) { | 619 | if (remaining_command_count == 0) { |
| 626 | adsp_rendered_event->Signal(); | 620 | adsp_rendered_event->Signal(); |
| 627 | } | 621 | } |
| 628 | } else { | 622 | } else { |
| 629 | adsp.ClearRemainCount(session_id); | 623 | audio_renderer.ClearRemainCommandCount(session_id); |
| 630 | terminate_event.Set(); | 624 | terminate_event.Set(); |
| 631 | } | 625 | } |
| 632 | } | 626 | } |
| @@ -635,7 +629,7 @@ void System::SendCommandToDsp() { | |||
| 635 | u64 System::GenerateCommand(std::span<u8> in_command_buffer, | 629 | u64 System::GenerateCommand(std::span<u8> in_command_buffer, |
| 636 | [[maybe_unused]] u64 command_buffer_size_) { | 630 | [[maybe_unused]] u64 command_buffer_size_) { |
| 637 | PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); | 631 | PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); |
| 638 | const auto start_time{core.CoreTiming().GetClockTicks()}; | 632 | const auto start_time{core.CoreTiming().GetGlobalTimeNs().count()}; |
| 639 | 633 | ||
| 640 | auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())}; | 634 | auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())}; |
| 641 | 635 | ||
| @@ -690,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, | |||
| 690 | sink_context, splitter_context, perf_manager}; | 684 | sink_context, splitter_context, perf_manager}; |
| 691 | 685 | ||
| 692 | voice_context.SortInfo(); | 686 | voice_context.SortInfo(); |
| 687 | command_generator.GenerateVoiceCommands(); | ||
| 693 | 688 | ||
| 694 | const auto start_estimated_time{drop_voice_param * | 689 | const auto start_estimated_time{drop_voice_param * |
| 695 | static_cast<f32>(command_buffer.estimated_process_time)}; | 690 | static_cast<f32>(command_buffer.estimated_process_time)}; |
| 696 | 691 | ||
| 697 | command_generator.GenerateVoiceCommands(); | ||
| 698 | command_generator.GenerateSubMixCommands(); | 692 | command_generator.GenerateSubMixCommands(); |
| 699 | command_generator.GenerateFinalMixCommands(); | 693 | command_generator.GenerateFinalMixCommands(); |
| 700 | command_generator.GenerateSinkCommands(); | 694 | command_generator.GenerateSinkCommands(); |
| @@ -714,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, | |||
| 714 | 708 | ||
| 715 | const auto end_estimated_time{drop_voice_param * | 709 | const auto end_estimated_time{drop_voice_param * |
| 716 | static_cast<f32>(command_buffer.estimated_process_time)}; | 710 | static_cast<f32>(command_buffer.estimated_process_time)}; |
| 711 | |||
| 712 | const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) * | ||
| 713 | (static_cast<f32>(render_time_limit_percent) / 100.0f)}; | ||
| 714 | |||
| 717 | const auto estimated_time{start_estimated_time - end_estimated_time}; | 715 | const auto estimated_time{start_estimated_time - end_estimated_time}; |
| 718 | 716 | ||
| 719 | const auto time_limit{static_cast<u32>( | 717 | const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))}; |
| 720 | estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) * | ||
| 721 | (static_cast<f32>(render_time_limit_percent) / 100.0f)))}; | ||
| 722 | num_voices_dropped = | 718 | num_voices_dropped = |
| 723 | DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); | 719 | DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); |
| 724 | } | 720 | } |
| @@ -732,10 +728,10 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, | |||
| 732 | effect_context.UpdateStateByDspShared(); | 728 | effect_context.UpdateStateByDspShared(); |
| 733 | } | 729 | } |
| 734 | 730 | ||
| 735 | const auto end_time{core.CoreTiming().GetClockTicks()}; | 731 | const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()}; |
| 736 | total_ticks_elapsed += end_time - start_time; | 732 | total_ticks_elapsed += end_time - start_time; |
| 737 | num_command_lists_generated++; | 733 | num_command_lists_generated++; |
| 738 | render_start_tick = adsp.GetRenderingStartTick(session_id); | 734 | render_start_tick = audio_renderer.GetRenderingStartTick(session_id); |
| 739 | frames_elapsed++; | 735 | frames_elapsed++; |
| 740 | 736 | ||
| 741 | return command_buffer.size; | 737 | return command_buffer.size; |
| @@ -778,7 +774,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time | |||
| 778 | while (i < command_buffer.count) { | 774 | while (i < command_buffer.count) { |
| 779 | const auto node_id{cmd->node_id}; | 775 | const auto node_id{cmd->node_id}; |
| 780 | const auto node_id_type{cmd->node_id >> 28}; | 776 | const auto node_id_type{cmd->node_id >> 28}; |
| 781 | const auto node_id_base{cmd->node_id & 0xFFF}; | 777 | const auto node_id_base{(cmd->node_id >> 16) & 0xFFF}; |
| 782 | 778 | ||
| 783 | // If the new estimated process time falls below the limit, we're done dropping. | 779 | // If the new estimated process time falls below the limit, we're done dropping. |
| 784 | if (estimated_process_time <= time_limit) { | 780 | if (estimated_process_time <= time_limit) { |
| @@ -819,4 +815,4 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time | |||
| 819 | return voices_dropped; | 815 | return voices_dropped; |
| 820 | } | 816 | } |
| 821 | 817 | ||
| 822 | } // namespace AudioCore::AudioRenderer | 818 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h index e328783b6..8a8341710 100644 --- a/src/audio_core/renderer/system.h +++ b/src/audio_core/renderer/system.h | |||
| @@ -34,12 +34,16 @@ class KTransferMemory; | |||
| 34 | 34 | ||
| 35 | namespace AudioCore { | 35 | namespace AudioCore { |
| 36 | struct AudioRendererParameterInternal; | 36 | struct AudioRendererParameterInternal; |
| 37 | |||
| 38 | namespace AudioRenderer { | ||
| 39 | class CommandBuffer; | ||
| 40 | namespace ADSP { | 37 | namespace ADSP { |
| 41 | class ADSP; | 38 | class ADSP; |
| 39 | namespace AudioRenderer { | ||
| 40 | class AudioRenderer; | ||
| 42 | } | 41 | } |
| 42 | } // namespace ADSP | ||
| 43 | |||
| 44 | namespace Renderer { | ||
| 45 | using namespace ::AudioCore::ADSP; | ||
| 46 | class CommandBuffer; | ||
| 43 | 47 | ||
| 44 | /** | 48 | /** |
| 45 | * Audio Renderer System, the main worker for audio rendering. | 49 | * Audio Renderer System, the main worker for audio rendering. |
| @@ -213,8 +217,8 @@ public: | |||
| 213 | private: | 217 | private: |
| 214 | /// Core system | 218 | /// Core system |
| 215 | Core::System& core; | 219 | Core::System& core; |
| 216 | /// Reference to the ADSP for communication | 220 | /// Reference to the ADSP's AudioRenderer for communication |
| 217 | ADSP::ADSP& adsp; | 221 | ::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer; |
| 218 | /// Is this system initialized? | 222 | /// Is this system initialized? |
| 219 | bool initialized{}; | 223 | bool initialized{}; |
| 220 | /// Is this system currently active? | 224 | /// Is this system currently active? |
| @@ -319,5 +323,5 @@ private: | |||
| 319 | f32 drop_voice_param{1.0f}; | 323 | f32 drop_voice_param{1.0f}; |
| 320 | }; | 324 | }; |
| 321 | 325 | ||
| 322 | } // namespace AudioRenderer | 326 | } // namespace Renderer |
| 323 | } // namespace AudioCore | 327 | } // namespace AudioCore |
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp index 300ecdbf1..a0b8ef29e 100644 --- a/src/audio_core/renderer/system_manager.cpp +++ b/src/audio_core/renderer/system_manager.cpp | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #include <chrono> | 4 | #include <chrono> |
| 5 | 5 | ||
| 6 | #include "audio_core/adsp/adsp.h" | ||
| 6 | #include "audio_core/audio_core.h" | 7 | #include "audio_core/audio_core.h" |
| 7 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 8 | #include "audio_core/renderer/system_manager.h" | 8 | #include "audio_core/renderer/system_manager.h" |
| 9 | #include "common/microprofile.h" | 9 | #include "common/microprofile.h" |
| 10 | #include "common/thread.h" | 10 | #include "common/thread.h" |
| @@ -14,24 +14,21 @@ | |||
| 14 | MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", | 14 | MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", |
| 15 | MP_RGB(60, 19, 97)); | 15 | MP_RGB(60, 19, 97)); |
| 16 | 16 | ||
| 17 | namespace AudioCore::AudioRenderer { | 17 | namespace AudioCore::Renderer { |
| 18 | 18 | ||
| 19 | SystemManager::SystemManager(Core::System& core_) | 19 | SystemManager::SystemManager(Core::System& core_) |
| 20 | : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()} {} | 20 | : core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {} |
| 21 | 21 | ||
| 22 | SystemManager::~SystemManager() { | 22 | SystemManager::~SystemManager() { |
| 23 | Stop(); | 23 | Stop(); |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | bool SystemManager::InitializeUnsafe() { | 26 | void SystemManager::InitializeUnsafe() { |
| 27 | if (!active) { | 27 | if (!active) { |
| 28 | if (adsp.Start()) { | 28 | active = true; |
| 29 | active = true; | 29 | audio_renderer.Start(); |
| 30 | thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); }); | 30 | thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); }); |
| 31 | } | ||
| 32 | } | 31 | } |
| 33 | |||
| 34 | return adsp.GetState() == ADSP::State::Started; | ||
| 35 | } | 32 | } |
| 36 | 33 | ||
| 37 | void SystemManager::Stop() { | 34 | void SystemManager::Stop() { |
| @@ -41,7 +38,7 @@ void SystemManager::Stop() { | |||
| 41 | active = false; | 38 | active = false; |
| 42 | thread.request_stop(); | 39 | thread.request_stop(); |
| 43 | thread.join(); | 40 | thread.join(); |
| 44 | adsp.Stop(); | 41 | audio_renderer.Stop(); |
| 45 | } | 42 | } |
| 46 | 43 | ||
| 47 | bool SystemManager::Add(System& system_) { | 44 | bool SystemManager::Add(System& system_) { |
| @@ -55,10 +52,7 @@ bool SystemManager::Add(System& system_) { | |||
| 55 | { | 52 | { |
| 56 | std::scoped_lock l{mutex1}; | 53 | std::scoped_lock l{mutex1}; |
| 57 | if (systems.empty()) { | 54 | if (systems.empty()) { |
| 58 | if (!InitializeUnsafe()) { | 55 | InitializeUnsafe(); |
| 59 | LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager"); | ||
| 60 | return false; | ||
| 61 | } | ||
| 62 | } | 56 | } |
| 63 | } | 57 | } |
| 64 | 58 | ||
| @@ -100,9 +94,9 @@ void SystemManager::ThreadFunc(std::stop_token stop_token) { | |||
| 100 | } | 94 | } |
| 101 | } | 95 | } |
| 102 | 96 | ||
| 103 | adsp.Signal(); | 97 | audio_renderer.Signal(); |
| 104 | adsp.Wait(); | 98 | audio_renderer.Wait(); |
| 105 | } | 99 | } |
| 106 | } | 100 | } |
| 107 | 101 | ||
| 108 | } // namespace AudioCore::AudioRenderer | 102 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h index 9681fd121..62e8e5f15 100644 --- a/src/audio_core/renderer/system_manager.h +++ b/src/audio_core/renderer/system_manager.h | |||
| @@ -18,11 +18,14 @@ struct EventType; | |||
| 18 | class System; | 18 | class System; |
| 19 | } // namespace Core | 19 | } // namespace Core |
| 20 | 20 | ||
| 21 | namespace AudioCore::AudioRenderer { | 21 | namespace AudioCore::ADSP { |
| 22 | namespace ADSP { | ||
| 23 | class ADSP; | 22 | class ADSP; |
| 24 | class AudioRenderer_Mailbox; | 23 | namespace AudioRenderer { |
| 25 | } // namespace ADSP | 24 | class AudioRenderer; |
| 25 | } // namespace AudioRenderer | ||
| 26 | } // namespace AudioCore::ADSP | ||
| 27 | |||
| 28 | namespace AudioCore::Renderer { | ||
| 26 | 29 | ||
| 27 | /** | 30 | /** |
| 28 | * Manages all audio renderers, responsible for triggering command list generation and signalling | 31 | * Manages all audio renderers, responsible for triggering command list generation and signalling |
| @@ -38,7 +41,7 @@ public: | |||
| 38 | * | 41 | * |
| 39 | * @return True if successfully initialized, otherwise false. | 42 | * @return True if successfully initialized, otherwise false. |
| 40 | */ | 43 | */ |
| 41 | bool InitializeUnsafe(); | 44 | void InitializeUnsafe(); |
| 42 | 45 | ||
| 43 | /** | 46 | /** |
| 44 | * Stop the system manager. | 47 | * Stop the system manager. |
| @@ -80,10 +83,8 @@ private: | |||
| 80 | std::mutex mutex2{}; | 83 | std::mutex mutex2{}; |
| 81 | /// Is the system manager thread active? | 84 | /// Is the system manager thread active? |
| 82 | std::atomic<bool> active{}; | 85 | std::atomic<bool> active{}; |
| 83 | /// Reference to the ADSP for communication | 86 | /// Reference to the ADSP's AudioRenderer for communication |
| 84 | ADSP::ADSP& adsp; | 87 | ::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer; |
| 85 | /// AudioRenderer mailbox for communication | ||
| 86 | ADSP::AudioRenderer_Mailbox* mailbox{}; | ||
| 87 | }; | 88 | }; |
| 88 | 89 | ||
| 89 | } // namespace AudioCore::AudioRenderer | 90 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h index a43c15af3..85c87f137 100644 --- a/src/audio_core/renderer/upsampler/upsampler_info.h +++ b/src/audio_core/renderer/upsampler/upsampler_info.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/upsampler/upsampler_state.h" | 9 | #include "audio_core/renderer/upsampler/upsampler_state.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | class UpsamplerManager; | 13 | class UpsamplerManager; |
| 14 | 14 | ||
| 15 | /** | 15 | /** |
| @@ -32,4 +32,4 @@ struct UpsamplerInfo { | |||
| 32 | std::array<s16, MaxChannels> inputs{}; | 32 | std::array<s16, MaxChannels> inputs{}; |
| 33 | }; | 33 | }; |
| 34 | 34 | ||
| 35 | } // namespace AudioCore::AudioRenderer | 35 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp index 4c76a5066..ef740f6c9 100644 --- a/src/audio_core/renderer/upsampler/upsampler_manager.cpp +++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | 4 | #include "audio_core/renderer/upsampler/upsampler_manager.h" |
| 5 | 5 | ||
| 6 | namespace AudioCore::AudioRenderer { | 6 | namespace AudioCore::Renderer { |
| 7 | 7 | ||
| 8 | UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_, | 8 | UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_, |
| 9 | std::span<s32> workbuffer_) | 9 | std::span<s32> workbuffer_) |
| @@ -41,4 +41,4 @@ void UpsamplerManager::Free(UpsamplerInfo* info) { | |||
| 41 | info->enabled = false; | 41 | info->enabled = false; |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | } // namespace AudioCore::AudioRenderer | 44 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h index 83c697c0c..263e5718b 100644 --- a/src/audio_core/renderer/upsampler/upsampler_manager.h +++ b/src/audio_core/renderer/upsampler/upsampler_manager.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "audio_core/renderer/upsampler/upsampler_info.h" | 9 | #include "audio_core/renderer/upsampler/upsampler_info.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * Manages and has utility functions for upsampler infos. | 14 | * Manages and has utility functions for upsampler infos. |
| 15 | */ | 15 | */ |
| @@ -42,4 +42,4 @@ private: | |||
| 42 | std::mutex lock{}; | 42 | std::mutex lock{}; |
| 43 | }; | 43 | }; |
| 44 | 44 | ||
| 45 | } // namespace AudioCore::AudioRenderer | 45 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h index 28cebe200..dc7b31d42 100644 --- a/src/audio_core/renderer/upsampler/upsampler_state.h +++ b/src/audio_core/renderer/upsampler/upsampler_state.h | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | #include "common/fixed_point.h" | 9 | #include "common/fixed_point.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | /** | 12 | /** |
| 13 | * Upsampling state used by the AudioRenderer across calls. | 13 | * Upsampling state used by the AudioRenderer across calls. |
| 14 | */ | 14 | */ |
| @@ -37,4 +37,4 @@ struct UpsamplerState { | |||
| 37 | u8 sample_index; | 37 | u8 sample_index; |
| 38 | }; | 38 | }; |
| 39 | 39 | ||
| 40 | } // namespace AudioCore::AudioRenderer | 40 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h index 26ab4ccce..4f19c2fcc 100644 --- a/src/audio_core/renderer/voice/voice_channel_resource.h +++ b/src/audio_core/renderer/voice/voice_channel_resource.h | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | #include "audio_core/common/common.h" | 8 | #include "audio_core/common/common.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace AudioCore::AudioRenderer { | 11 | namespace AudioCore::Renderer { |
| 12 | /** | 12 | /** |
| 13 | * Represents one channel for mixing a voice. | 13 | * Represents one channel for mixing a voice. |
| 14 | */ | 14 | */ |
| @@ -35,4 +35,4 @@ public: | |||
| 35 | bool in_use{}; | 35 | bool in_use{}; |
| 36 | }; | 36 | }; |
| 37 | 37 | ||
| 38 | } // namespace AudioCore::AudioRenderer | 38 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp index 16a3e839d..c3644e38b 100644 --- a/src/audio_core/renderer/voice/voice_context.cpp +++ b/src/audio_core/renderer/voice/voice_context.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/voice/voice_context.h" | 6 | #include "audio_core/renderer/voice/voice_context.h" |
| 7 | #include "common/polyfill_ranges.h" | 7 | #include "common/polyfill_ranges.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | VoiceState& VoiceContext::GetDspSharedState(const u32 index) { | 11 | VoiceState& VoiceContext::GetDspSharedState(const u32 index) { |
| 12 | if (index >= dsp_states.size()) { | 12 | if (index >= dsp_states.size()) { |
| @@ -84,4 +84,4 @@ void VoiceContext::UpdateStateByDspShared() { | |||
| 84 | std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState)); | 84 | std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState)); |
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | } // namespace AudioCore::AudioRenderer | 87 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h index 43b677154..138ab2773 100644 --- a/src/audio_core/renderer/voice/voice_context.h +++ b/src/audio_core/renderer/voice/voice_context.h | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include "audio_core/renderer/voice/voice_state.h" | 10 | #include "audio_core/renderer/voice/voice_state.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | 12 | ||
| 13 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::Renderer { |
| 14 | /** | 14 | /** |
| 15 | * Contains all voices, with utility functions for managing them. | 15 | * Contains all voices, with utility functions for managing them. |
| 16 | */ | 16 | */ |
| @@ -123,4 +123,4 @@ private: | |||
| 123 | u32 active_count{}; | 123 | u32 active_count{}; |
| 124 | }; | 124 | }; |
| 125 | 125 | ||
| 126 | } // namespace AudioCore::AudioRenderer | 126 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp index c0bfb23fc..6239cfab7 100644 --- a/src/audio_core/renderer/voice/voice_info.cpp +++ b/src/audio_core/renderer/voice/voice_info.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include "audio_core/renderer/voice/voice_info.h" | 6 | #include "audio_core/renderer/voice/voice_info.h" |
| 7 | #include "audio_core/renderer/voice/voice_state.h" | 7 | #include "audio_core/renderer/voice/voice_state.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::Renderer { |
| 10 | 10 | ||
| 11 | VoiceInfo::VoiceInfo() { | 11 | VoiceInfo::VoiceInfo() { |
| 12 | Initialize(); | 12 | Initialize(); |
| @@ -405,4 +405,4 @@ void VoiceInfo::ResetResources(VoiceContext& voice_context) const { | |||
| 405 | } | 405 | } |
| 406 | } | 406 | } |
| 407 | 407 | ||
| 408 | } // namespace AudioCore::AudioRenderer | 408 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 3c5d3e04f..14a687dcb 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.h | |||
| @@ -12,7 +12,7 @@ | |||
| 12 | #include "audio_core/renderer/memory/address_info.h" | 12 | #include "audio_core/renderer/memory/address_info.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | 14 | ||
| 15 | namespace AudioCore::AudioRenderer { | 15 | namespace AudioCore::Renderer { |
| 16 | class PoolMapper; | 16 | class PoolMapper; |
| 17 | class VoiceContext; | 17 | class VoiceContext; |
| 18 | struct VoiceState; | 18 | struct VoiceState; |
| @@ -377,4 +377,4 @@ public: | |||
| 377 | u8 flush_buffer_count{}; | 377 | u8 flush_buffer_count{}; |
| 378 | }; | 378 | }; |
| 379 | 379 | ||
| 380 | } // namespace AudioCore::AudioRenderer | 380 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h index ce947233f..c7aee167b 100644 --- a/src/audio_core/renderer/voice/voice_state.h +++ b/src/audio_core/renderer/voice/voice_state.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/fixed_point.h" | 10 | #include "common/fixed_point.h" |
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::Renderer { |
| 13 | /** | 13 | /** |
| 14 | * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer, | 14 | * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer, |
| 15 | * host-side is updated on the next iteration. | 15 | * host-side is updated on the next iteration. |
| @@ -67,4 +67,4 @@ struct VoiceState { | |||
| 67 | s32 loop_count; | 67 | s32 loop_count; |
| 68 | }; | 68 | }; |
| 69 | 69 | ||
| 70 | } // namespace AudioCore::AudioRenderer | 70 | } // namespace AudioCore::Renderer |
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 9a0801888..bbb598bc5 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include "audio_core/sink/cubeb_sink.h" | 8 | #include "audio_core/sink/cubeb_sink.h" |
| 9 | #include "audio_core/sink/sink_stream.h" | 9 | #include "audio_core/sink/sink_stream.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/scope_exit.h" | ||
| 11 | #include "core/core.h" | 12 | #include "core/core.h" |
| 12 | 13 | ||
| 13 | #ifdef _WIN32 | 14 | #ifdef _WIN32 |
| @@ -332,25 +333,38 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) { | |||
| 332 | return device_list; | 333 | return device_list; |
| 333 | } | 334 | } |
| 334 | 335 | ||
| 335 | u32 GetCubebLatency() { | 336 | namespace { |
| 336 | cubeb* ctx; | 337 | static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) { |
| 338 | return TargetSampleCount; | ||
| 339 | } | ||
| 340 | static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {} | ||
| 341 | } // namespace | ||
| 342 | |||
| 343 | bool IsCubebSuitable() { | ||
| 344 | #if !defined(HAVE_CUBEB) | ||
| 345 | return false; | ||
| 346 | #else | ||
| 347 | cubeb* ctx{nullptr}; | ||
| 337 | 348 | ||
| 338 | #ifdef _WIN32 | 349 | #ifdef _WIN32 |
| 339 | auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | 350 | auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); |
| 340 | #endif | 351 | #endif |
| 341 | 352 | ||
| 353 | // Init cubeb | ||
| 342 | if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { | 354 | if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { |
| 343 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | 355 | LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable."); |
| 344 | // Return a large latency so we choose SDL instead. | 356 | return false; |
| 345 | return 10000u; | ||
| 346 | } | 357 | } |
| 347 | 358 | ||
| 359 | SCOPE_EXIT({ cubeb_destroy(ctx); }); | ||
| 360 | |||
| 348 | #ifdef _WIN32 | 361 | #ifdef _WIN32 |
| 349 | if (SUCCEEDED(com_init_result)) { | 362 | if (SUCCEEDED(com_init_result)) { |
| 350 | CoUninitialize(); | 363 | CoUninitialize(); |
| 351 | } | 364 | } |
| 352 | #endif | 365 | #endif |
| 353 | 366 | ||
| 367 | // Get min latency | ||
| 354 | cubeb_stream_params params{}; | 368 | cubeb_stream_params params{}; |
| 355 | params.rate = TargetSampleRate; | 369 | params.rate = TargetSampleRate; |
| 356 | params.channels = 2; | 370 | params.channels = 2; |
| @@ -361,12 +375,27 @@ u32 GetCubebLatency() { | |||
| 361 | u32 latency{0}; | 375 | u32 latency{0}; |
| 362 | const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency); | 376 | const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency); |
| 363 | if (latency_error != CUBEB_OK) { | 377 | if (latency_error != CUBEB_OK) { |
| 364 | LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); | 378 | LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable."); |
| 365 | latency = TargetSampleCount * 2; | 379 | return false; |
| 366 | } | 380 | } |
| 367 | latency = std::max(latency, TargetSampleCount * 2); | 381 | latency = std::max(latency, TargetSampleCount * 2); |
| 368 | cubeb_destroy(ctx); | 382 | |
| 369 | return latency; | 383 | // Test opening a device with standard parameters |
| 384 | cubeb_devid output_device{0}; | ||
| 385 | cubeb_devid input_device{0}; | ||
| 386 | std::string name{"Yuzu test"}; | ||
| 387 | cubeb_stream* stream{nullptr}; | ||
| 388 | |||
| 389 | if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, ¶ms, | ||
| 390 | latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) { | ||
| 391 | LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable."); | ||
| 392 | return false; | ||
| 393 | } | ||
| 394 | |||
| 395 | cubeb_stream_stop(stream); | ||
| 396 | cubeb_stream_destroy(stream); | ||
| 397 | return true; | ||
| 398 | #endif | ||
| 370 | } | 399 | } |
| 371 | 400 | ||
| 372 | } // namespace AudioCore::Sink | 401 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index 3302cb98d..f49a6fdaa 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h | |||
| @@ -97,10 +97,11 @@ private: | |||
| 97 | std::vector<std::string> ListCubebSinkDevices(bool capture); | 97 | std::vector<std::string> ListCubebSinkDevices(bool capture); |
| 98 | 98 | ||
| 99 | /** | 99 | /** |
| 100 | * Get the reported latency for this sink. | 100 | * Check if this backend is suitable for use. |
| 101 | * Checks if enabled, its latency, whether it opens successfully, etc. | ||
| 101 | * | 102 | * |
| 102 | * @return Minimum latency for this sink. | 103 | * @return True is this backend is suitable, false otherwise. |
| 103 | */ | 104 | */ |
| 104 | u32 GetCubebLatency(); | 105 | bool IsCubebSuitable(); |
| 105 | 106 | ||
| 106 | } // namespace AudioCore::Sink | 107 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index c1529d1f9..7b89151de 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "audio_core/sink/sdl2_sink.h" | 9 | #include "audio_core/sink/sdl2_sink.h" |
| 10 | #include "audio_core/sink/sink_stream.h" | 10 | #include "audio_core/sink/sink_stream.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/scope_exit.h" | ||
| 12 | #include "core/core.h" | 13 | #include "core/core.h" |
| 13 | 14 | ||
| 14 | namespace AudioCore::Sink { | 15 | namespace AudioCore::Sink { |
| @@ -84,6 +85,7 @@ public: | |||
| 84 | } | 85 | } |
| 85 | 86 | ||
| 86 | Stop(); | 87 | Stop(); |
| 88 | SDL_ClearQueuedAudio(device); | ||
| 87 | SDL_CloseAudioDevice(device); | 89 | SDL_CloseAudioDevice(device); |
| 88 | } | 90 | } |
| 89 | 91 | ||
| @@ -227,8 +229,42 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) { | |||
| 227 | return device_list; | 229 | return device_list; |
| 228 | } | 230 | } |
| 229 | 231 | ||
| 230 | u32 GetSDLLatency() { | 232 | bool IsSDLSuitable() { |
| 231 | return TargetSampleCount * 2; | 233 | #if !defined(HAVE_SDL2) |
| 234 | return false; | ||
| 235 | #else | ||
| 236 | // Check SDL can init | ||
| 237 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||
| 238 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||
| 239 | LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}", | ||
| 240 | SDL_GetError()); | ||
| 241 | return false; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | // We can set any latency frequency we want with SDL, so no need to check that. | ||
| 246 | |||
| 247 | // Check we can open a device with standard parameters | ||
| 248 | SDL_AudioSpec spec; | ||
| 249 | spec.freq = TargetSampleRate; | ||
| 250 | spec.channels = 2u; | ||
| 251 | spec.format = AUDIO_S16SYS; | ||
| 252 | spec.samples = TargetSampleCount * 2; | ||
| 253 | spec.callback = nullptr; | ||
| 254 | spec.userdata = nullptr; | ||
| 255 | |||
| 256 | SDL_AudioSpec obtained; | ||
| 257 | auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false); | ||
| 258 | |||
| 259 | if (device == 0) { | ||
| 260 | LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}", | ||
| 261 | SDL_GetError()); | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | SDL_CloseAudioDevice(device); | ||
| 266 | return true; | ||
| 267 | #endif | ||
| 232 | } | 268 | } |
| 233 | 269 | ||
| 234 | } // namespace AudioCore::Sink | 270 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index 27ed1ab94..9211d2e97 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h | |||
| @@ -88,10 +88,11 @@ private: | |||
| 88 | std::vector<std::string> ListSDLSinkDevices(bool capture); | 88 | std::vector<std::string> ListSDLSinkDevices(bool capture); |
| 89 | 89 | ||
| 90 | /** | 90 | /** |
| 91 | * Get the reported latency for this sink. | 91 | * Check if this backend is suitable for use. |
| 92 | * Checks if enabled, its latency, whether it opens successfully, etc. | ||
| 92 | * | 93 | * |
| 93 | * @return Minimum latency for this sink. | 94 | * @return True is this backend is suitable, false otherwise. |
| 94 | */ | 95 | */ |
| 95 | u32 GetSDLLatency(); | 96 | bool IsSDLSuitable(); |
| 96 | 97 | ||
| 97 | } // namespace AudioCore::Sink | 98 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 027bfa517..7c9a4e3ac 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp | |||
| @@ -22,7 +22,7 @@ namespace { | |||
| 22 | struct SinkDetails { | 22 | struct SinkDetails { |
| 23 | using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | 23 | using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); |
| 24 | using ListDevicesFn = std::vector<std::string> (*)(bool); | 24 | using ListDevicesFn = std::vector<std::string> (*)(bool); |
| 25 | using LatencyFn = u32 (*)(); | 25 | using SuitableFn = bool (*)(); |
| 26 | 26 | ||
| 27 | /// Name for this sink. | 27 | /// Name for this sink. |
| 28 | Settings::AudioEngine id; | 28 | Settings::AudioEngine id; |
| @@ -30,8 +30,8 @@ struct SinkDetails { | |||
| 30 | FactoryFn factory; | 30 | FactoryFn factory; |
| 31 | /// A method to call to list available devices. | 31 | /// A method to call to list available devices. |
| 32 | ListDevicesFn list_devices; | 32 | ListDevicesFn list_devices; |
| 33 | /// Method to get the latency of this backend. | 33 | /// Check whether this backend is suitable to be used. |
| 34 | LatencyFn latency; | 34 | SuitableFn is_suitable; |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | // sink_details is ordered in terms of desirability, with the best choice at the top. | 37 | // sink_details is ordered in terms of desirability, with the best choice at the top. |
| @@ -43,7 +43,7 @@ constexpr SinkDetails sink_details[] = { | |||
| 43 | return std::make_unique<CubebSink>(device_id); | 43 | return std::make_unique<CubebSink>(device_id); |
| 44 | }, | 44 | }, |
| 45 | &ListCubebSinkDevices, | 45 | &ListCubebSinkDevices, |
| 46 | &GetCubebLatency, | 46 | &IsCubebSuitable, |
| 47 | }, | 47 | }, |
| 48 | #endif | 48 | #endif |
| 49 | #ifdef HAVE_SDL2 | 49 | #ifdef HAVE_SDL2 |
| @@ -53,14 +53,17 @@ constexpr SinkDetails sink_details[] = { | |||
| 53 | return std::make_unique<SDLSink>(device_id); | 53 | return std::make_unique<SDLSink>(device_id); |
| 54 | }, | 54 | }, |
| 55 | &ListSDLSinkDevices, | 55 | &ListSDLSinkDevices, |
| 56 | &GetSDLLatency, | 56 | &IsSDLSuitable, |
| 57 | }, | 57 | }, |
| 58 | #endif | 58 | #endif |
| 59 | SinkDetails{Settings::AudioEngine::Null, | 59 | SinkDetails{ |
| 60 | [](std::string_view device_id) -> std::unique_ptr<Sink> { | 60 | Settings::AudioEngine::Null, |
| 61 | return std::make_unique<NullSink>(device_id); | 61 | [](std::string_view device_id) -> std::unique_ptr<Sink> { |
| 62 | }, | 62 | return std::make_unique<NullSink>(device_id); |
| 63 | [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }}, | 63 | }, |
| 64 | [](bool capture) { return std::vector<std::string>{"null"}; }, | ||
| 65 | []() { return true; }, | ||
| 66 | }, | ||
| 64 | }; | 67 | }; |
| 65 | 68 | ||
| 66 | const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { | 69 | const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { |
| @@ -72,18 +75,22 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { | |||
| 72 | auto iter = find_backend(sink_id); | 75 | auto iter = find_backend(sink_id); |
| 73 | 76 | ||
| 74 | if (sink_id == Settings::AudioEngine::Auto) { | 77 | if (sink_id == Settings::AudioEngine::Auto) { |
| 75 | // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which | 78 | // Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking |
| 76 | // causes audio issues, in that case go with SDL. | 79 | // that the backend is available and suitable to use. |
| 77 | #if defined(HAVE_CUBEB) && defined(HAVE_SDL2) | 80 | for (auto& details : sink_details) { |
| 78 | iter = find_backend(Settings::AudioEngine::Cubeb); | 81 | if (details.is_suitable()) { |
| 79 | if (iter->latency() > TargetSampleCount * 3) { | 82 | iter = &details; |
| 80 | iter = find_backend(Settings::AudioEngine::Sdl2); | 83 | break; |
| 84 | } | ||
| 81 | } | 85 | } |
| 82 | #else | ||
| 83 | iter = std::begin(sink_details); | ||
| 84 | #endif | ||
| 85 | LOG_INFO(Service_Audio, "Auto-selecting the {} backend", | 86 | LOG_INFO(Service_Audio, "Auto-selecting the {} backend", |
| 86 | Settings::CanonicalizeEnum(iter->id)); | 87 | Settings::CanonicalizeEnum(iter->id)); |
| 88 | } else { | ||
| 89 | if (iter != std::end(sink_details) && !iter->is_suitable()) { | ||
| 90 | LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null", | ||
| 91 | Settings::CanonicalizeEnum(iter->id)); | ||
| 92 | iter = find_backend(Settings::AudioEngine::Null); | ||
| 93 | } | ||
| 87 | } | 94 | } |
| 88 | 95 | ||
| 89 | if (iter == std::end(sink_details)) { | 96 | if (iter == std::end(sink_details)) { |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index bf97d9ba2..416203c59 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -26,12 +26,11 @@ add_library(common STATIC | |||
| 26 | assert.h | 26 | assert.h |
| 27 | atomic_helpers.h | 27 | atomic_helpers.h |
| 28 | atomic_ops.h | 28 | atomic_ops.h |
| 29 | detached_tasks.cpp | ||
| 30 | detached_tasks.h | ||
| 31 | bit_cast.h | 29 | bit_cast.h |
| 32 | bit_field.h | 30 | bit_field.h |
| 33 | bit_set.h | 31 | bit_set.h |
| 34 | bit_util.h | 32 | bit_util.h |
| 33 | bounded_threadsafe_queue.h | ||
| 35 | cityhash.cpp | 34 | cityhash.cpp |
| 36 | cityhash.h | 35 | cityhash.h |
| 37 | common_funcs.h | 36 | common_funcs.h |
| @@ -41,6 +40,8 @@ add_library(common STATIC | |||
| 41 | container_hash.h | 40 | container_hash.h |
| 42 | demangle.cpp | 41 | demangle.cpp |
| 43 | demangle.h | 42 | demangle.h |
| 43 | detached_tasks.cpp | ||
| 44 | detached_tasks.h | ||
| 44 | div_ceil.h | 45 | div_ceil.h |
| 45 | dynamic_library.cpp | 46 | dynamic_library.cpp |
| 46 | dynamic_library.h | 47 | dynamic_library.h |
| @@ -151,6 +152,10 @@ add_library(common STATIC | |||
| 151 | zstd_compression.h | 152 | zstd_compression.h |
| 152 | ) | 153 | ) |
| 153 | 154 | ||
| 155 | if (YUZU_ENABLE_PORTABLE) | ||
| 156 | add_compile_definitions(YUZU_ENABLE_PORTABLE) | ||
| 157 | endif() | ||
| 158 | |||
| 154 | if (WIN32) | 159 | if (WIN32) |
| 155 | target_sources(common PRIVATE | 160 | target_sources(common PRIVATE |
| 156 | windows/timer_resolution.cpp | 161 | windows/timer_resolution.cpp |
| @@ -191,8 +196,6 @@ if (MSVC) | |||
| 191 | _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING | 196 | _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING |
| 192 | ) | 197 | ) |
| 193 | target_compile_options(common PRIVATE | 198 | target_compile_options(common PRIVATE |
| 194 | /W4 | ||
| 195 | |||
| 196 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data | 199 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data |
| 197 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data | 200 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data |
| 198 | /we4800 # Implicit conversion from 'type' to bool. Possible information loss | 201 | /we4800 # Implicit conversion from 'type' to bool. Possible information loss |
diff --git a/src/common/alignment.h b/src/common/alignment.h index fa715d497..fc5c26898 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <bit> | ||
| 6 | #include <cstddef> | 7 | #include <cstddef> |
| 7 | #include <new> | 8 | #include <new> |
| 8 | #include <type_traits> | 9 | #include <type_traits> |
| @@ -10,8 +11,10 @@ | |||
| 10 | namespace Common { | 11 | namespace Common { |
| 11 | 12 | ||
| 12 | template <typename T> | 13 | template <typename T> |
| 13 | requires std::is_unsigned_v<T> | 14 | requires std::is_integral_v<T> |
| 14 | [[nodiscard]] constexpr T AlignUp(T value, size_t size) { | 15 | [[nodiscard]] constexpr T AlignUp(T value_, size_t size) { |
| 16 | using U = typename std::make_unsigned_t<T>; | ||
| 17 | auto value{static_cast<U>(value_)}; | ||
| 15 | auto mod{static_cast<T>(value % size)}; | 18 | auto mod{static_cast<T>(value % size)}; |
| 16 | value -= mod; | 19 | value -= mod; |
| 17 | return static_cast<T>(mod == T{0} ? value : value + size); | 20 | return static_cast<T>(mod == T{0} ? value : value + size); |
| @@ -24,8 +27,10 @@ template <typename T> | |||
| 24 | } | 27 | } |
| 25 | 28 | ||
| 26 | template <typename T> | 29 | template <typename T> |
| 27 | requires std::is_unsigned_v<T> | 30 | requires std::is_integral_v<T> |
| 28 | [[nodiscard]] constexpr T AlignDown(T value, size_t size) { | 31 | [[nodiscard]] constexpr T AlignDown(T value_, size_t size) { |
| 32 | using U = typename std::make_unsigned_t<T>; | ||
| 33 | const auto value{static_cast<U>(value_)}; | ||
| 29 | return static_cast<T>(value - value % size); | 34 | return static_cast<T>(value - value % size); |
| 30 | } | 35 | } |
| 31 | 36 | ||
| @@ -55,6 +60,30 @@ template <typename T, typename U> | |||
| 55 | return (x + (y - 1)) / y; | 60 | return (x + (y - 1)) / y; |
| 56 | } | 61 | } |
| 57 | 62 | ||
| 63 | template <typename T> | ||
| 64 | requires std::is_integral_v<T> | ||
| 65 | [[nodiscard]] constexpr T LeastSignificantOneBit(T x) { | ||
| 66 | return x & ~(x - 1); | ||
| 67 | } | ||
| 68 | |||
| 69 | template <typename T> | ||
| 70 | requires std::is_integral_v<T> | ||
| 71 | [[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) { | ||
| 72 | return x & (x - 1); | ||
| 73 | } | ||
| 74 | |||
| 75 | template <typename T> | ||
| 76 | requires std::is_integral_v<T> | ||
| 77 | [[nodiscard]] constexpr bool IsPowerOfTwo(T x) { | ||
| 78 | return x > 0 && ResetLeastSignificantOneBit(x) == 0; | ||
| 79 | } | ||
| 80 | |||
| 81 | template <typename T> | ||
| 82 | requires std::is_integral_v<T> | ||
| 83 | [[nodiscard]] constexpr T FloorPowerOfTwo(T x) { | ||
| 84 | return T{1} << (sizeof(T) * 8 - std::countl_zero(x) - 1); | ||
| 85 | } | ||
| 86 | |||
| 58 | template <typename T, size_t Align = 16> | 87 | template <typename T, size_t Align = 16> |
| 59 | class AlignmentAllocator { | 88 | class AlignmentAllocator { |
| 60 | public: | 89 | public: |
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index bd87aa09b..b36fc1de9 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h | |||
| @@ -45,13 +45,13 @@ public: | |||
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | T PopWait() { | 47 | T PopWait() { |
| 48 | T t; | 48 | T t{}; |
| 49 | Pop<PopMode::Wait>(t); | 49 | Pop<PopMode::Wait>(t); |
| 50 | return t; | 50 | return t; |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | T PopWait(std::stop_token stop_token) { | 53 | T PopWait(std::stop_token stop_token) { |
| 54 | T t; | 54 | T t{}; |
| 55 | Pop<PopMode::WaitWithStopToken>(t, stop_token); | 55 | Pop<PopMode::WaitWithStopToken>(t, stop_token); |
| 56 | return t; | 56 | return t; |
| 57 | } | 57 | } |
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index 36e67c145..174aed49b 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp | |||
| @@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, | |||
| 528 | // Generic Filesystem Operations | 528 | // Generic Filesystem Operations |
| 529 | 529 | ||
| 530 | bool Exists(const fs::path& path) { | 530 | bool Exists(const fs::path& path) { |
| 531 | std::error_code ec; | ||
| 531 | #ifdef ANDROID | 532 | #ifdef ANDROID |
| 532 | if (Android::IsContentUri(path)) { | 533 | if (Android::IsContentUri(path)) { |
| 533 | return Android::Exists(path); | 534 | return Android::Exists(path); |
| 534 | } else { | 535 | } else { |
| 535 | return fs::exists(path); | 536 | return fs::exists(path, ec); |
| 536 | } | 537 | } |
| 537 | #else | 538 | #else |
| 538 | return fs::exists(path); | 539 | return fs::exists(path, ec); |
| 539 | #endif | 540 | #endif |
| 540 | } | 541 | } |
| 541 | 542 | ||
| 542 | bool IsFile(const fs::path& path) { | 543 | bool IsFile(const fs::path& path) { |
| 544 | std::error_code ec; | ||
| 543 | #ifdef ANDROID | 545 | #ifdef ANDROID |
| 544 | if (Android::IsContentUri(path)) { | 546 | if (Android::IsContentUri(path)) { |
| 545 | return !Android::IsDirectory(path); | 547 | return !Android::IsDirectory(path); |
| 546 | } else { | 548 | } else { |
| 547 | return fs::is_regular_file(path); | 549 | return fs::is_regular_file(path, ec); |
| 548 | } | 550 | } |
| 549 | #else | 551 | #else |
| 550 | return fs::is_regular_file(path); | 552 | return fs::is_regular_file(path, ec); |
| 551 | #endif | 553 | #endif |
| 552 | } | 554 | } |
| 553 | 555 | ||
| 554 | bool IsDir(const fs::path& path) { | 556 | bool IsDir(const fs::path& path) { |
| 557 | std::error_code ec; | ||
| 555 | #ifdef ANDROID | 558 | #ifdef ANDROID |
| 556 | if (Android::IsContentUri(path)) { | 559 | if (Android::IsContentUri(path)) { |
| 557 | return Android::IsDirectory(path); | 560 | return Android::IsDirectory(path); |
| 558 | } else { | 561 | } else { |
| 559 | return fs::is_directory(path); | 562 | return fs::is_directory(path, ec); |
| 560 | } | 563 | } |
| 561 | #else | 564 | #else |
| 562 | return fs::is_directory(path); | 565 | return fs::is_directory(path, ec); |
| 563 | #endif | 566 | #endif |
| 564 | } | 567 | } |
| 565 | 568 | ||
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index d71cfacc6..dce219fcf 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp | |||
| @@ -88,8 +88,9 @@ public: | |||
| 88 | fs::path yuzu_path_config; | 88 | fs::path yuzu_path_config; |
| 89 | 89 | ||
| 90 | #ifdef _WIN32 | 90 | #ifdef _WIN32 |
| 91 | #ifdef YUZU_ENABLE_PORTABLE | ||
| 91 | yuzu_path = GetExeDirectory() / PORTABLE_DIR; | 92 | yuzu_path = GetExeDirectory() / PORTABLE_DIR; |
| 92 | 93 | #endif | |
| 93 | if (!IsDir(yuzu_path)) { | 94 | if (!IsDir(yuzu_path)) { |
| 94 | yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; | 95 | yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; |
| 95 | } | 96 | } |
| @@ -101,8 +102,9 @@ public: | |||
| 101 | yuzu_path_cache = yuzu_path / CACHE_DIR; | 102 | yuzu_path_cache = yuzu_path / CACHE_DIR; |
| 102 | yuzu_path_config = yuzu_path / CONFIG_DIR; | 103 | yuzu_path_config = yuzu_path / CONFIG_DIR; |
| 103 | #else | 104 | #else |
| 105 | #ifdef YUZU_ENABLE_PORTABLE | ||
| 104 | yuzu_path = GetCurrentDir() / PORTABLE_DIR; | 106 | yuzu_path = GetCurrentDir() / PORTABLE_DIR; |
| 105 | 107 | #endif | |
| 106 | if (Exists(yuzu_path) && IsDir(yuzu_path)) { | 108 | if (Exists(yuzu_path) && IsDir(yuzu_path)) { |
| 107 | yuzu_path_cache = yuzu_path / CACHE_DIR; | 109 | yuzu_path_cache = yuzu_path / CACHE_DIR; |
| 108 | yuzu_path_config = yuzu_path / CONFIG_DIR; | 110 | yuzu_path_config = yuzu_path / CONFIG_DIR; |
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index c95909561..4e3a614a4 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp | |||
| @@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { | |||
| 112 | SUB(Service, NCM) \ | 112 | SUB(Service, NCM) \ |
| 113 | SUB(Service, NFC) \ | 113 | SUB(Service, NFC) \ |
| 114 | SUB(Service, NFP) \ | 114 | SUB(Service, NFP) \ |
| 115 | SUB(Service, NGCT) \ | 115 | SUB(Service, NGC) \ |
| 116 | SUB(Service, NIFM) \ | 116 | SUB(Service, NIFM) \ |
| 117 | SUB(Service, NIM) \ | 117 | SUB(Service, NIM) \ |
| 118 | SUB(Service, NOTIF) \ | 118 | SUB(Service, NOTIF) \ |
diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 8356e3183..08af50ee0 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h | |||
| @@ -80,7 +80,7 @@ enum class Class : u8 { | |||
| 80 | Service_NCM, ///< The NCM service | 80 | Service_NCM, ///< The NCM service |
| 81 | Service_NFC, ///< The NFC (Near-field communication) service | 81 | Service_NFC, ///< The NFC (Near-field communication) service |
| 82 | Service_NFP, ///< The NFP service | 82 | Service_NFP, ///< The NFP service |
| 83 | Service_NGCT, ///< The NGCT (No Good Content for Terra) service | 83 | Service_NGC, ///< The NGC (No Good Content) service |
| 84 | Service_NIFM, ///< The NIFM (Network interface) service | 84 | Service_NIFM, ///< The NIFM (Network interface) service |
| 85 | Service_NIM, ///< The NIM service | 85 | Service_NIM, ///< The NIM service |
| 86 | Service_NOTIF, ///< The NOTIF (Notification) service | 86 | Service_NOTIF, ///< The NOTIF (Notification) service |
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp index ffb32fecf..d85ab1742 100644 --- a/src/common/lz4_compression.cpp +++ b/src/common/lz4_compression.cpp | |||
| @@ -71,4 +71,10 @@ std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t un | |||
| 71 | return uncompressed; | 71 | return uncompressed; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 75 | // This is just a thin wrapper around LZ4. | ||
| 76 | return LZ4_decompress_safe(reinterpret_cast<const char*>(src), reinterpret_cast<char*>(dst), | ||
| 77 | static_cast<int>(src_size), static_cast<int>(dst_size)); | ||
| 78 | } | ||
| 79 | |||
| 74 | } // namespace Common::Compression | 80 | } // namespace Common::Compression |
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h index 7fd53a960..3ae17c2bb 100644 --- a/src/common/lz4_compression.h +++ b/src/common/lz4_compression.h | |||
| @@ -56,4 +56,6 @@ namespace Common::Compression { | |||
| 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, | 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, |
| 57 | std::size_t uncompressed_size); | 57 | std::size_t uncompressed_size); |
| 58 | 58 | ||
| 59 | [[nodiscard]] int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size); | ||
| 60 | |||
| 59 | } // namespace Common::Compression | 61 | } // namespace Common::Compression |
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h index b5ef055db..41cbb9ed5 100644 --- a/src/common/polyfill_thread.h +++ b/src/common/polyfill_thread.h | |||
| @@ -19,8 +19,8 @@ | |||
| 19 | namespace Common { | 19 | namespace Common { |
| 20 | 20 | ||
| 21 | template <typename Condvar, typename Lock, typename Pred> | 21 | template <typename Condvar, typename Lock, typename Pred> |
| 22 | void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { | 22 | void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { |
| 23 | cv.wait(lock, token, std::move(pred)); | 23 | cv.wait(lk, token, std::move(pred)); |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | template <typename Rep, typename Period> | 26 | template <typename Rep, typename Period> |
| @@ -332,13 +332,17 @@ private: | |||
| 332 | namespace Common { | 332 | namespace Common { |
| 333 | 333 | ||
| 334 | template <typename Condvar, typename Lock, typename Pred> | 334 | template <typename Condvar, typename Lock, typename Pred> |
| 335 | void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { | 335 | void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) { |
| 336 | if (token.stop_requested()) { | 336 | if (token.stop_requested()) { |
| 337 | return; | 337 | return; |
| 338 | } | 338 | } |
| 339 | 339 | ||
| 340 | std::stop_callback callback(token, [&] { cv.notify_all(); }); | 340 | std::stop_callback callback(token, [&] { |
| 341 | cv.wait(lock, [&] { return pred() || token.stop_requested(); }); | 341 | { std::scoped_lock lk2{*lk.mutex()}; } |
| 342 | cv.notify_all(); | ||
| 343 | }); | ||
| 344 | |||
| 345 | cv.wait(lk, [&] { return pred() || token.stop_requested(); }); | ||
| 342 | } | 346 | } |
| 343 | 347 | ||
| 344 | template <typename Rep, typename Period> | 348 | template <typename Rep, typename Period> |
| @@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, | |||
| 353 | 357 | ||
| 354 | std::stop_callback cb(token, [&] { | 358 | std::stop_callback cb(token, [&] { |
| 355 | // Wake up the waiting thread. | 359 | // Wake up the waiting thread. |
| 356 | std::unique_lock lk{m}; | 360 | { |
| 357 | stop_requested = true; | 361 | std::scoped_lock lk{m}; |
| 362 | stop_requested = true; | ||
| 363 | } | ||
| 358 | cv.notify_one(); | 364 | cv.notify_one(); |
| 359 | }); | 365 | }); |
| 360 | 366 | ||
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 15fd2e222..4ecaf550b 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <version> | 4 | #include <version> |
| 5 | #include "common/settings_enums.h" | ||
| 5 | #if __cpp_lib_chrono >= 201907L | 6 | #if __cpp_lib_chrono >= 201907L |
| 6 | #include <chrono> | 7 | #include <chrono> |
| 7 | #include <exception> | 8 | #include <exception> |
| @@ -145,6 +146,10 @@ bool IsFastmemEnabled() { | |||
| 145 | return true; | 146 | return true; |
| 146 | } | 147 | } |
| 147 | 148 | ||
| 149 | bool IsDockedMode() { | ||
| 150 | return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked; | ||
| 151 | } | ||
| 152 | |||
| 148 | float Volume() { | 153 | float Volume() { |
| 149 | if (values.audio_muted) { | 154 | if (values.audio_muted) { |
| 150 | return 0.0f; | 155 | return 0.0f; |
| @@ -154,6 +159,8 @@ float Volume() { | |||
| 154 | 159 | ||
| 155 | const char* TranslateCategory(Category category) { | 160 | const char* TranslateCategory(Category category) { |
| 156 | switch (category) { | 161 | switch (category) { |
| 162 | case Category::Android: | ||
| 163 | return "Android"; | ||
| 157 | case Category::Audio: | 164 | case Category::Audio: |
| 158 | return "Audio"; | 165 | return "Audio"; |
| 159 | case Category::Core: | 166 | case Category::Core: |
| @@ -207,9 +214,7 @@ const char* TranslateCategory(Category category) { | |||
| 207 | return "Miscellaneous"; | 214 | return "Miscellaneous"; |
| 208 | } | 215 | } |
| 209 | 216 | ||
| 210 | void UpdateRescalingInfo() { | 217 | void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) { |
| 211 | const auto setup = values.resolution_setup.GetValue(); | ||
| 212 | auto& info = values.resolution_info; | ||
| 213 | info.downscale = false; | 218 | info.downscale = false; |
| 214 | switch (setup) { | 219 | switch (setup) { |
| 215 | case ResolutionSetup::Res1_2X: | 220 | case ResolutionSetup::Res1_2X: |
| @@ -269,6 +274,12 @@ void UpdateRescalingInfo() { | |||
| 269 | info.active = info.up_scale != 1 || info.down_shift != 0; | 274 | info.active = info.up_scale != 1 || info.down_shift != 0; |
| 270 | } | 275 | } |
| 271 | 276 | ||
| 277 | void UpdateRescalingInfo() { | ||
| 278 | const auto setup = values.resolution_setup.GetValue(); | ||
| 279 | auto& info = values.resolution_info; | ||
| 280 | TranslateResolutionInfo(setup, info); | ||
| 281 | } | ||
| 282 | |||
| 272 | void RestoreGlobalState(bool is_powered_on) { | 283 | void RestoreGlobalState(bool is_powered_on) { |
| 273 | // If a game is running, DO NOT restore the global settings state | 284 | // If a game is running, DO NOT restore the global settings state |
| 274 | if (is_powered_on) { | 285 | if (is_powered_on) { |
diff --git a/src/common/settings.h b/src/common/settings.h index b0bc6519a..82ec9077e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -348,6 +348,8 @@ struct Values { | |||
| 348 | Category::RendererDebug}; | 348 | Category::RendererDebug}; |
| 349 | Setting<bool> disable_shader_loop_safety_checks{ | 349 | Setting<bool> disable_shader_loop_safety_checks{ |
| 350 | linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; | 350 | linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; |
| 351 | Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", | ||
| 352 | Category::RendererDebug}; | ||
| 351 | 353 | ||
| 352 | // System | 354 | // System |
| 353 | SwitchableSetting<Language, true> language_index{linkage, | 355 | SwitchableSetting<Language, true> language_index{linkage, |
| @@ -379,7 +381,13 @@ struct Values { | |||
| 379 | 381 | ||
| 380 | Setting<s32> current_user{linkage, 0, "current_user", Category::System}; | 382 | Setting<s32> current_user{linkage, 0, "current_user", Category::System}; |
| 381 | 383 | ||
| 382 | SwitchableSetting<bool> use_docked_mode{linkage, true, "use_docked_mode", Category::System}; | 384 | SwitchableSetting<ConsoleMode> use_docked_mode{linkage, |
| 385 | ConsoleMode::Docked, | ||
| 386 | "use_docked_mode", | ||
| 387 | Category::System, | ||
| 388 | Specialization::Radio, | ||
| 389 | true, | ||
| 390 | true}; | ||
| 383 | 391 | ||
| 384 | // Controls | 392 | // Controls |
| 385 | InputSetting<std::array<PlayerInput, 10>> players; | 393 | InputSetting<std::array<PlayerInput, 10>> players; |
| @@ -519,12 +527,15 @@ bool IsGPULevelHigh(); | |||
| 519 | 527 | ||
| 520 | bool IsFastmemEnabled(); | 528 | bool IsFastmemEnabled(); |
| 521 | 529 | ||
| 530 | bool IsDockedMode(); | ||
| 531 | |||
| 522 | float Volume(); | 532 | float Volume(); |
| 523 | 533 | ||
| 524 | std::string GetTimeZoneString(TimeZone time_zone); | 534 | std::string GetTimeZoneString(TimeZone time_zone); |
| 525 | 535 | ||
| 526 | void LogSettings(); | 536 | void LogSettings(); |
| 527 | 537 | ||
| 538 | void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info); | ||
| 528 | void UpdateRescalingInfo(); | 539 | void UpdateRescalingInfo(); |
| 529 | 540 | ||
| 530 | // Restore the global state of all applicable settings in the Values struct | 541 | // Restore the global state of all applicable settings in the Values struct |
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp index dedf5ef90..5960b78aa 100644 --- a/src/common/settings_common.cpp +++ b/src/common/settings_common.cpp | |||
| @@ -1,7 +1,9 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <functional> | ||
| 4 | #include <string> | 5 | #include <string> |
| 6 | #include <vector> | ||
| 5 | #include "common/settings_common.h" | 7 | #include "common/settings_common.h" |
| 6 | 8 | ||
| 7 | namespace Settings { | 9 | namespace Settings { |
| @@ -12,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ | |||
| 12 | : label{name}, category{category_}, id{linkage.count}, save{save_}, | 14 | : label{name}, category{category_}, id{linkage.count}, save{save_}, |
| 13 | runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, | 15 | runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, |
| 14 | other_setting{other_setting_} { | 16 | other_setting{other_setting_} { |
| 17 | linkage.by_key.insert({name, this}); | ||
| 15 | linkage.by_category[category].push_back(this); | 18 | linkage.by_category[category].push_back(this); |
| 16 | linkage.count++; | 19 | linkage.count++; |
| 17 | } | 20 | } |
diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 2efb329b0..1800ab10d 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | namespace Settings { | 12 | namespace Settings { |
| 13 | 13 | ||
| 14 | enum class Category : u32 { | 14 | enum class Category : u32 { |
| 15 | Android, | ||
| 15 | Audio, | 16 | Audio, |
| 16 | Core, | 17 | Core, |
| 17 | Cpu, | 18 | Cpu, |
| @@ -56,6 +57,7 @@ enum Specialization : u8 { | |||
| 56 | Scalar = 5, // Values are continuous | 57 | Scalar = 5, // Values are continuous |
| 57 | Countable = 6, // Can be stepped through | 58 | Countable = 6, // Can be stepped through |
| 58 | Paired = 7, // Another setting is associated with this setting | 59 | Paired = 7, // Another setting is associated with this setting |
| 60 | Radio = 8, // Setting should be presented in a radio group | ||
| 59 | 61 | ||
| 60 | Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage | 62 | Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage |
| 61 | }; | 63 | }; |
| @@ -67,6 +69,7 @@ public: | |||
| 67 | explicit Linkage(u32 initial_count = 0); | 69 | explicit Linkage(u32 initial_count = 0); |
| 68 | ~Linkage(); | 70 | ~Linkage(); |
| 69 | std::map<Category, std::vector<BasicSetting*>> by_category{}; | 71 | std::map<Category, std::vector<BasicSetting*>> by_category{}; |
| 72 | std::map<std::string, Settings::BasicSetting*> by_key{}; | ||
| 70 | std::vector<std::function<void()>> restore_functions{}; | 73 | std::vector<std::function<void()>> restore_functions{}; |
| 71 | u32 count; | 74 | u32 count; |
| 72 | }; | 75 | }; |
| @@ -222,6 +225,16 @@ public: | |||
| 222 | */ | 225 | */ |
| 223 | [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; | 226 | [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; |
| 224 | 227 | ||
| 228 | /** | ||
| 229 | * @returns True if the underlying type is a floating point storage | ||
| 230 | */ | ||
| 231 | [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0; | ||
| 232 | |||
| 233 | /** | ||
| 234 | * @returns True if the underlying type is an integer storage | ||
| 235 | */ | ||
| 236 | [[nodiscard]] virtual constexpr bool IsIntegral() const = 0; | ||
| 237 | |||
| 225 | /* | 238 | /* |
| 226 | * Switchable settings | 239 | * Switchable settings |
| 227 | */ | 240 | */ |
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index a1a29ebf6..815cafe15 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h | |||
| @@ -12,8 +12,8 @@ namespace Settings { | |||
| 12 | 12 | ||
| 13 | template <typename T> | 13 | template <typename T> |
| 14 | struct EnumMetadata { | 14 | struct EnumMetadata { |
| 15 | static constexpr std::vector<std::pair<std::string, T>> Canonicalizations(); | 15 | static std::vector<std::pair<std::string, T>> Canonicalizations(); |
| 16 | static constexpr u32 Index(); | 16 | static u32 Index(); |
| 17 | }; | 17 | }; |
| 18 | 18 | ||
| 19 | #define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__)) | 19 | #define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__)) |
| @@ -66,11 +66,11 @@ struct EnumMetadata { | |||
| 66 | #define ENUM(NAME, ...) \ | 66 | #define ENUM(NAME, ...) \ |
| 67 | enum class NAME : u32 { __VA_ARGS__ }; \ | 67 | enum class NAME : u32 { __VA_ARGS__ }; \ |
| 68 | template <> \ | 68 | template <> \ |
| 69 | constexpr std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ | 69 | inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ |
| 70 | return {PAIR(NAME, __VA_ARGS__)}; \ | 70 | return {PAIR(NAME, __VA_ARGS__)}; \ |
| 71 | } \ | 71 | } \ |
| 72 | template <> \ | 72 | template <> \ |
| 73 | constexpr u32 EnumMetadata<NAME>::Index() { \ | 73 | inline u32 EnumMetadata<NAME>::Index() { \ |
| 74 | return __COUNTER__; \ | 74 | return __COUNTER__; \ |
| 75 | } | 75 | } |
| 76 | 76 | ||
| @@ -85,7 +85,7 @@ enum class AudioEngine : u32 { | |||
| 85 | }; | 85 | }; |
| 86 | 86 | ||
| 87 | template <> | 87 | template <> |
| 88 | constexpr std::vector<std::pair<std::string, AudioEngine>> | 88 | inline std::vector<std::pair<std::string, AudioEngine>> |
| 89 | EnumMetadata<AudioEngine>::Canonicalizations() { | 89 | EnumMetadata<AudioEngine>::Canonicalizations() { |
| 90 | return { | 90 | return { |
| 91 | {"auto", AudioEngine::Auto}, | 91 | {"auto", AudioEngine::Auto}, |
| @@ -96,7 +96,7 @@ EnumMetadata<AudioEngine>::Canonicalizations() { | |||
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | template <> | 98 | template <> |
| 99 | constexpr u32 EnumMetadata<AudioEngine>::Index() { | 99 | inline u32 EnumMetadata<AudioEngine>::Index() { |
| 100 | // This is just a sufficiently large number that is more than the number of other enums declared | 100 | // This is just a sufficiently large number that is more than the number of other enums declared |
| 101 | // here | 101 | // here |
| 102 | return 100; | 102 | return 100; |
| @@ -146,8 +146,10 @@ ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); | |||
| 146 | 146 | ||
| 147 | ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); | 147 | ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); |
| 148 | 148 | ||
| 149 | ENUM(ConsoleMode, Handheld, Docked); | ||
| 150 | |||
| 149 | template <typename Type> | 151 | template <typename Type> |
| 150 | constexpr std::string CanonicalizeEnum(Type id) { | 152 | inline std::string CanonicalizeEnum(Type id) { |
| 151 | const auto group = EnumMetadata<Type>::Canonicalizations(); | 153 | const auto group = EnumMetadata<Type>::Canonicalizations(); |
| 152 | for (auto& [name, value] : group) { | 154 | for (auto& [name, value] : group) { |
| 153 | if (value == id) { | 155 | if (value == id) { |
| @@ -158,7 +160,7 @@ constexpr std::string CanonicalizeEnum(Type id) { | |||
| 158 | } | 160 | } |
| 159 | 161 | ||
| 160 | template <typename Type> | 162 | template <typename Type> |
| 161 | constexpr Type ToEnum(const std::string& canonicalization) { | 163 | inline Type ToEnum(const std::string& canonicalization) { |
| 162 | const auto group = EnumMetadata<Type>::Canonicalizations(); | 164 | const auto group = EnumMetadata<Type>::Canonicalizations(); |
| 163 | for (auto& [name, value] : group) { | 165 | for (auto& [name, value] : group) { |
| 164 | if (name == canonicalization) { | 166 | if (name == canonicalization) { |
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h index a8beb06e9..7be6f26f7 100644 --- a/src/common/settings_setting.h +++ b/src/common/settings_setting.h | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include <string> | 10 | #include <string> |
| 11 | #include <typeindex> | 11 | #include <typeindex> |
| 12 | #include <typeinfo> | 12 | #include <typeinfo> |
| 13 | #include <fmt/core.h> | ||
| 13 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 14 | #include "common/settings_common.h" | 15 | #include "common/settings_common.h" |
| 15 | #include "common/settings_enums.h" | 16 | #include "common/settings_enums.h" |
| @@ -115,8 +116,12 @@ protected: | |||
| 115 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { | 116 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { |
| 116 | // Compatibility with old AudioEngine setting being a string | 117 | // Compatibility with old AudioEngine setting being a string |
| 117 | return CanonicalizeEnum(value_); | 118 | return CanonicalizeEnum(value_); |
| 119 | } else if constexpr (std::is_floating_point_v<Type>) { | ||
| 120 | return fmt::format("{:f}", value_); | ||
| 121 | } else if constexpr (std::is_enum_v<Type>) { | ||
| 122 | return std::to_string(static_cast<u32>(value_)); | ||
| 118 | } else { | 123 | } else { |
| 119 | return std::to_string(static_cast<u64>(value_)); | 124 | return std::to_string(value_); |
| 120 | } | 125 | } |
| 121 | } | 126 | } |
| 122 | 127 | ||
| @@ -180,17 +185,19 @@ public: | |||
| 180 | this->SetValue(static_cast<u32>(std::stoul(input))); | 185 | this->SetValue(static_cast<u32>(std::stoul(input))); |
| 181 | } else if constexpr (std::is_same_v<Type, bool>) { | 186 | } else if constexpr (std::is_same_v<Type, bool>) { |
| 182 | this->SetValue(input == "true"); | 187 | this->SetValue(input == "true"); |
| 183 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { | 188 | } else if constexpr (std::is_same_v<Type, float>) { |
| 184 | this->SetValue(ToEnum<Type>(input)); | 189 | this->SetValue(std::stof(input)); |
| 185 | } else { | 190 | } else { |
| 186 | this->SetValue(static_cast<Type>(std::stoll(input))); | 191 | this->SetValue(static_cast<Type>(std::stoll(input))); |
| 187 | } | 192 | } |
| 188 | } catch (std::invalid_argument&) { | 193 | } catch (std::invalid_argument&) { |
| 189 | this->SetValue(this->GetDefault()); | 194 | this->SetValue(this->GetDefault()); |
| 195 | } catch (std::out_of_range&) { | ||
| 196 | this->SetValue(this->GetDefault()); | ||
| 190 | } | 197 | } |
| 191 | } | 198 | } |
| 192 | 199 | ||
| 193 | [[nodiscard]] std::string constexpr Canonicalize() const override final { | 200 | [[nodiscard]] std::string Canonicalize() const override final { |
| 194 | if constexpr (std::is_enum_v<Type>) { | 201 | if constexpr (std::is_enum_v<Type>) { |
| 195 | return CanonicalizeEnum(this->GetValue()); | 202 | return CanonicalizeEnum(this->GetValue()); |
| 196 | } else { | 203 | } else { |
| @@ -215,11 +222,27 @@ public: | |||
| 215 | } | 222 | } |
| 216 | } | 223 | } |
| 217 | 224 | ||
| 225 | [[nodiscard]] constexpr bool IsFloatingPoint() const final { | ||
| 226 | return std::is_floating_point_v<Type>; | ||
| 227 | } | ||
| 228 | |||
| 229 | [[nodiscard]] constexpr bool IsIntegral() const final { | ||
| 230 | return std::is_integral_v<Type>; | ||
| 231 | } | ||
| 232 | |||
| 218 | [[nodiscard]] std::string MinVal() const override final { | 233 | [[nodiscard]] std::string MinVal() const override final { |
| 219 | return this->ToString(minimum); | 234 | if constexpr (std::is_arithmetic_v<Type> && !ranged) { |
| 235 | return this->ToString(std::numeric_limits<Type>::min()); | ||
| 236 | } else { | ||
| 237 | return this->ToString(minimum); | ||
| 238 | } | ||
| 220 | } | 239 | } |
| 221 | [[nodiscard]] std::string MaxVal() const override final { | 240 | [[nodiscard]] std::string MaxVal() const override final { |
| 222 | return this->ToString(maximum); | 241 | if constexpr (std::is_arithmetic_v<Type> && !ranged) { |
| 242 | return this->ToString(std::numeric_limits<Type>::max()); | ||
| 243 | } else { | ||
| 244 | return this->ToString(maximum); | ||
| 245 | } | ||
| 223 | } | 246 | } |
| 224 | 247 | ||
| 225 | [[nodiscard]] constexpr bool Ranged() const override { | 248 | [[nodiscard]] constexpr bool Ranged() const override { |
| @@ -256,11 +279,11 @@ public: | |||
| 256 | * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded | 279 | * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded |
| 257 | * @param other_setting_ A second Setting to associate to this one in metadata | 280 | * @param other_setting_ A second Setting to associate to this one in metadata |
| 258 | */ | 281 | */ |
| 282 | template <typename T = BasicSetting> | ||
| 259 | explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, | 283 | explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, |
| 260 | Category category_, u32 specialization_ = Specialization::Default, | 284 | Category category_, u32 specialization_ = Specialization::Default, |
| 261 | bool save_ = true, bool runtime_modifiable_ = false, | 285 | bool save_ = true, bool runtime_modifiable_ = false, |
| 262 | BasicSetting* other_setting_ = nullptr) | 286 | typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr) |
| 263 | requires(!ranged) | ||
| 264 | : Setting<Type, false>{ | 287 | : Setting<Type, false>{ |
| 265 | linkage, default_val, name, category_, specialization_, | 288 | linkage, default_val, name, category_, specialization_, |
| 266 | save_, runtime_modifiable_, other_setting_} { | 289 | save_, runtime_modifiable_, other_setting_} { |
| @@ -282,12 +305,12 @@ public: | |||
| 282 | * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded | 305 | * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded |
| 283 | * @param other_setting_ A second Setting to associate to this one in metadata | 306 | * @param other_setting_ A second Setting to associate to this one in metadata |
| 284 | */ | 307 | */ |
| 308 | template <typename T = BasicSetting> | ||
| 285 | explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, | 309 | explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, |
| 286 | const Type& max_val, const std::string& name, Category category_, | 310 | const Type& max_val, const std::string& name, Category category_, |
| 287 | u32 specialization_ = Specialization::Default, bool save_ = true, | 311 | u32 specialization_ = Specialization::Default, bool save_ = true, |
| 288 | bool runtime_modifiable_ = false, | 312 | bool runtime_modifiable_ = false, |
| 289 | BasicSetting* other_setting_ = nullptr) | 313 | typename std::enable_if<ranged, T*>::type other_setting_ = nullptr) |
| 290 | requires(ranged) | ||
| 291 | : Setting<Type, true>{linkage, default_val, min_val, | 314 | : Setting<Type, true>{linkage, default_val, min_val, |
| 292 | max_val, name, category_, | 315 | max_val, name, category_, |
| 293 | specialization_, save_, runtime_modifiable_, | 316 | specialization_, save_, runtime_modifiable_, |
diff --git a/src/common/swap.h b/src/common/swap.h index 085baaf9a..fde343e45 100644 --- a/src/common/swap.h +++ b/src/common/swap.h | |||
| @@ -460,11 +460,6 @@ S operator&(const S& i, const swap_struct_t<T, F> v) { | |||
| 460 | return i & v.swap(); | 460 | return i & v.swap(); |
| 461 | } | 461 | } |
| 462 | 462 | ||
| 463 | template <typename S, typename T, typename F> | ||
| 464 | S operator&(const swap_struct_t<T, F> v, const S& i) { | ||
| 465 | return static_cast<S>(v.swap() & i); | ||
| 466 | } | ||
| 467 | |||
| 468 | // Comparison | 463 | // Comparison |
| 469 | template <typename S, typename T, typename F> | 464 | template <typename S, typename T, typename F> |
| 470 | bool operator<(const S& p, const swap_struct_t<T, F> v) { | 465 | bool operator<(const S& p, const swap_struct_t<T, F> v) { |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4b7395be8..b2dc71d4c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -37,6 +37,49 @@ add_library(core STATIC | |||
| 37 | debugger/gdbstub.h | 37 | debugger/gdbstub.h |
| 38 | device_memory.cpp | 38 | device_memory.cpp |
| 39 | device_memory.h | 39 | device_memory.h |
| 40 | file_sys/fssystem/fs_i_storage.h | ||
| 41 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | ||
| 42 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | ||
| 43 | file_sys/fssystem/fssystem_aes_ctr_storage.cpp | ||
| 44 | file_sys/fssystem/fssystem_aes_ctr_storage.h | ||
| 45 | file_sys/fssystem/fssystem_aes_xts_storage.cpp | ||
| 46 | file_sys/fssystem/fssystem_aes_xts_storage.h | ||
| 47 | file_sys/fssystem/fssystem_alignment_matching_storage.h | ||
| 48 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | ||
| 49 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | ||
| 50 | file_sys/fssystem/fssystem_bucket_tree.cpp | ||
| 51 | file_sys/fssystem/fssystem_bucket_tree.h | ||
| 52 | file_sys/fssystem/fssystem_bucket_tree_utils.h | ||
| 53 | file_sys/fssystem/fssystem_compressed_storage.h | ||
| 54 | file_sys/fssystem/fssystem_compression_common.h | ||
| 55 | file_sys/fssystem/fssystem_compression_configuration.cpp | ||
| 56 | file_sys/fssystem/fssystem_compression_configuration.h | ||
| 57 | file_sys/fssystem/fssystem_crypto_configuration.cpp | ||
| 58 | file_sys/fssystem/fssystem_crypto_configuration.h | ||
| 59 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | ||
| 60 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | ||
| 61 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | ||
| 62 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | ||
| 63 | file_sys/fssystem/fssystem_indirect_storage.cpp | ||
| 64 | file_sys/fssystem/fssystem_indirect_storage.h | ||
| 65 | file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | ||
| 66 | file_sys/fssystem/fssystem_integrity_romfs_storage.h | ||
| 67 | file_sys/fssystem/fssystem_integrity_verification_storage.cpp | ||
| 68 | file_sys/fssystem/fssystem_integrity_verification_storage.h | ||
| 69 | file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | ||
| 70 | file_sys/fssystem/fssystem_nca_file_system_driver.cpp | ||
| 71 | file_sys/fssystem/fssystem_nca_file_system_driver.h | ||
| 72 | file_sys/fssystem/fssystem_nca_header.cpp | ||
| 73 | file_sys/fssystem/fssystem_nca_header.h | ||
| 74 | file_sys/fssystem/fssystem_nca_reader.cpp | ||
| 75 | file_sys/fssystem/fssystem_pooled_buffer.cpp | ||
| 76 | file_sys/fssystem/fssystem_pooled_buffer.h | ||
| 77 | file_sys/fssystem/fssystem_sparse_storage.cpp | ||
| 78 | file_sys/fssystem/fssystem_sparse_storage.h | ||
| 79 | file_sys/fssystem/fssystem_switch_storage.h | ||
| 80 | file_sys/fssystem/fssystem_utility.cpp | ||
| 81 | file_sys/fssystem/fssystem_utility.h | ||
| 82 | file_sys/fssystem/fs_types.h | ||
| 40 | file_sys/bis_factory.cpp | 83 | file_sys/bis_factory.cpp |
| 41 | file_sys/bis_factory.h | 84 | file_sys/bis_factory.h |
| 42 | file_sys/card_image.cpp | 85 | file_sys/card_image.cpp |
| @@ -57,8 +100,6 @@ add_library(core STATIC | |||
| 57 | file_sys/mode.h | 100 | file_sys/mode.h |
| 58 | file_sys/nca_metadata.cpp | 101 | file_sys/nca_metadata.cpp |
| 59 | file_sys/nca_metadata.h | 102 | file_sys/nca_metadata.h |
| 60 | file_sys/nca_patch.cpp | ||
| 61 | file_sys/nca_patch.h | ||
| 62 | file_sys/partition_filesystem.cpp | 103 | file_sys/partition_filesystem.cpp |
| 63 | file_sys/partition_filesystem.h | 104 | file_sys/partition_filesystem.h |
| 64 | file_sys/patch_manager.cpp | 105 | file_sys/patch_manager.cpp |
| @@ -543,13 +584,23 @@ add_library(core STATIC | |||
| 543 | hle/service/lm/lm.h | 584 | hle/service/lm/lm.h |
| 544 | hle/service/mig/mig.cpp | 585 | hle/service/mig/mig.cpp |
| 545 | hle/service/mig/mig.h | 586 | hle/service/mig/mig.h |
| 587 | hle/service/mii/types/char_info.cpp | ||
| 588 | hle/service/mii/types/char_info.h | ||
| 589 | hle/service/mii/types/core_data.cpp | ||
| 590 | hle/service/mii/types/core_data.h | ||
| 591 | hle/service/mii/types/raw_data.cpp | ||
| 592 | hle/service/mii/types/raw_data.h | ||
| 593 | hle/service/mii/types/store_data.cpp | ||
| 594 | hle/service/mii/types/store_data.h | ||
| 595 | hle/service/mii/types/ver3_store_data.cpp | ||
| 596 | hle/service/mii/types/ver3_store_data.h | ||
| 546 | hle/service/mii/mii.cpp | 597 | hle/service/mii/mii.cpp |
| 547 | hle/service/mii/mii.h | 598 | hle/service/mii/mii.h |
| 548 | hle/service/mii/mii_manager.cpp | 599 | hle/service/mii/mii_manager.cpp |
| 549 | hle/service/mii/mii_manager.h | 600 | hle/service/mii/mii_manager.h |
| 550 | hle/service/mii/raw_data.cpp | 601 | hle/service/mii/mii_result.h |
| 551 | hle/service/mii/raw_data.h | 602 | hle/service/mii/mii_types.h |
| 552 | hle/service/mii/types.h | 603 | hle/service/mii/mii_util.h |
| 553 | hle/service/mm/mm_u.cpp | 604 | hle/service/mm/mm_u.cpp |
| 554 | hle/service/mm/mm_u.h | 605 | hle/service/mm/mm_u.h |
| 555 | hle/service/mnpp/mnpp_app.cpp | 606 | hle/service/mnpp/mnpp_app.cpp |
| @@ -576,8 +627,8 @@ add_library(core STATIC | |||
| 576 | hle/service/nfp/nfp_interface.h | 627 | hle/service/nfp/nfp_interface.h |
| 577 | hle/service/nfp/nfp_result.h | 628 | hle/service/nfp/nfp_result.h |
| 578 | hle/service/nfp/nfp_types.h | 629 | hle/service/nfp/nfp_types.h |
| 579 | hle/service/ngct/ngct.cpp | 630 | hle/service/ngc/ngc.cpp |
| 580 | hle/service/ngct/ngct.h | 631 | hle/service/ngc/ngc.h |
| 581 | hle/service/nifm/nifm.cpp | 632 | hle/service/nifm/nifm.cpp |
| 582 | hle/service/nifm/nifm.h | 633 | hle/service/nifm/nifm.h |
| 583 | hle/service/nim/nim.cpp | 634 | hle/service/nim/nim.cpp |
| @@ -813,6 +864,8 @@ add_library(core STATIC | |||
| 813 | telemetry_session.h | 864 | telemetry_session.h |
| 814 | tools/freezer.cpp | 865 | tools/freezer.cpp |
| 815 | tools/freezer.h | 866 | tools/freezer.h |
| 867 | tools/renderdoc.cpp | ||
| 868 | tools/renderdoc.h | ||
| 816 | ) | 869 | ) |
| 817 | 870 | ||
| 818 | if (MSVC) | 871 | if (MSVC) |
| @@ -828,6 +881,7 @@ else() | |||
| 828 | -Werror=conversion | 881 | -Werror=conversion |
| 829 | 882 | ||
| 830 | -Wno-sign-conversion | 883 | -Wno-sign-conversion |
| 884 | -Wno-cast-function-type | ||
| 831 | 885 | ||
| 832 | $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> | 886 | $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> |
| 833 | ) | 887 | ) |
| @@ -836,7 +890,7 @@ endif() | |||
| 836 | create_target_directory_groups(core) | 890 | create_target_directory_groups(core) |
| 837 | 891 | ||
| 838 | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) | 892 | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) |
| 839 | target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) | 893 | target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc) |
| 840 | if (MINGW) | 894 | if (MINGW) |
| 841 | target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) | 895 | target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) |
| 842 | endif() | 896 | endif() |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 2f67e60a9..e8300cd05 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -51,6 +51,7 @@ | |||
| 51 | #include "core/reporter.h" | 51 | #include "core/reporter.h" |
| 52 | #include "core/telemetry_session.h" | 52 | #include "core/telemetry_session.h" |
| 53 | #include "core/tools/freezer.h" | 53 | #include "core/tools/freezer.h" |
| 54 | #include "core/tools/renderdoc.h" | ||
| 54 | #include "network/network.h" | 55 | #include "network/network.h" |
| 55 | #include "video_core/host1x/host1x.h" | 56 | #include "video_core/host1x/host1x.h" |
| 56 | #include "video_core/renderer_base.h" | 57 | #include "video_core/renderer_base.h" |
| @@ -273,13 +274,18 @@ struct System::Impl { | |||
| 273 | time_manager.Initialize(); | 274 | time_manager.Initialize(); |
| 274 | 275 | ||
| 275 | is_powered_on = true; | 276 | is_powered_on = true; |
| 276 | exit_lock = false; | 277 | exit_locked = false; |
| 278 | exit_requested = false; | ||
| 277 | 279 | ||
| 278 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); | 280 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); |
| 279 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); | 281 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); |
| 280 | microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); | 282 | microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); |
| 281 | microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); | 283 | microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); |
| 282 | 284 | ||
| 285 | if (Settings::values.enable_renderdoc_hotkey) { | ||
| 286 | renderdoc_api = std::make_unique<Tools::RenderdocAPI>(); | ||
| 287 | } | ||
| 288 | |||
| 283 | LOG_DEBUG(Core, "Initialized OK"); | 289 | LOG_DEBUG(Core, "Initialized OK"); |
| 284 | 290 | ||
| 285 | return SystemResultStatus::Success; | 291 | return SystemResultStatus::Success; |
| @@ -398,12 +404,14 @@ struct System::Impl { | |||
| 398 | } | 404 | } |
| 399 | 405 | ||
| 400 | is_powered_on = false; | 406 | is_powered_on = false; |
| 401 | exit_lock = false; | 407 | exit_locked = false; |
| 408 | exit_requested = false; | ||
| 402 | 409 | ||
| 403 | if (gpu_core != nullptr) { | 410 | if (gpu_core != nullptr) { |
| 404 | gpu_core->NotifyShutdown(); | 411 | gpu_core->NotifyShutdown(); |
| 405 | } | 412 | } |
| 406 | 413 | ||
| 414 | Network::CancelPendingSocketOperations(); | ||
| 407 | kernel.SuspendApplication(true); | 415 | kernel.SuspendApplication(true); |
| 408 | if (services) { | 416 | if (services) { |
| 409 | services->KillNVNFlinger(); | 417 | services->KillNVNFlinger(); |
| @@ -425,6 +433,7 @@ struct System::Impl { | |||
| 425 | debugger.reset(); | 433 | debugger.reset(); |
| 426 | kernel.Shutdown(); | 434 | kernel.Shutdown(); |
| 427 | memory.Reset(); | 435 | memory.Reset(); |
| 436 | Network::RestartSocketOperations(); | ||
| 428 | 437 | ||
| 429 | if (auto room_member = room_network.GetRoomMember().lock()) { | 438 | if (auto room_member = room_network.GetRoomMember().lock()) { |
| 430 | Network::GameInfo game_info{}; | 439 | Network::GameInfo game_info{}; |
| @@ -507,7 +516,8 @@ struct System::Impl { | |||
| 507 | 516 | ||
| 508 | CpuManager cpu_manager; | 517 | CpuManager cpu_manager; |
| 509 | std::atomic_bool is_powered_on{}; | 518 | std::atomic_bool is_powered_on{}; |
| 510 | bool exit_lock = false; | 519 | bool exit_locked = false; |
| 520 | bool exit_requested = false; | ||
| 511 | 521 | ||
| 512 | bool nvdec_active{}; | 522 | bool nvdec_active{}; |
| 513 | 523 | ||
| @@ -516,6 +526,8 @@ struct System::Impl { | |||
| 516 | std::unique_ptr<Tools::Freezer> memory_freezer; | 526 | std::unique_ptr<Tools::Freezer> memory_freezer; |
| 517 | std::array<u8, 0x20> build_id{}; | 527 | std::array<u8, 0x20> build_id{}; |
| 518 | 528 | ||
| 529 | std::unique_ptr<Tools::RenderdocAPI> renderdoc_api; | ||
| 530 | |||
| 519 | /// Frontend applets | 531 | /// Frontend applets |
| 520 | Service::AM::Applets::AppletManager applet_manager; | 532 | Service::AM::Applets::AppletManager applet_manager; |
| 521 | 533 | ||
| @@ -559,6 +571,8 @@ struct System::Impl { | |||
| 559 | 571 | ||
| 560 | std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> | 572 | std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> |
| 561 | gpu_dirty_memory_write_manager{}; | 573 | gpu_dirty_memory_write_manager{}; |
| 574 | |||
| 575 | std::deque<std::vector<u8>> user_channel; | ||
| 562 | }; | 576 | }; |
| 563 | 577 | ||
| 564 | System::System() : impl{std::make_unique<Impl>(*this)} {} | 578 | System::System() : impl{std::make_unique<Impl>(*this)} {} |
| @@ -943,12 +957,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const { | |||
| 943 | return impl->time_manager; | 957 | return impl->time_manager; |
| 944 | } | 958 | } |
| 945 | 959 | ||
| 946 | void System::SetExitLock(bool locked) { | 960 | void System::SetExitLocked(bool locked) { |
| 947 | impl->exit_lock = locked; | 961 | impl->exit_locked = locked; |
| 962 | } | ||
| 963 | |||
| 964 | bool System::GetExitLocked() const { | ||
| 965 | return impl->exit_locked; | ||
| 948 | } | 966 | } |
| 949 | 967 | ||
| 950 | bool System::GetExitLock() const { | 968 | void System::SetExitRequested(bool requested) { |
| 951 | return impl->exit_lock; | 969 | impl->exit_requested = requested; |
| 970 | } | ||
| 971 | |||
| 972 | bool System::GetExitRequested() const { | ||
| 973 | return impl->exit_requested; | ||
| 952 | } | 974 | } |
| 953 | 975 | ||
| 954 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { | 976 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { |
| @@ -1009,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const { | |||
| 1009 | return impl->room_network; | 1031 | return impl->room_network; |
| 1010 | } | 1032 | } |
| 1011 | 1033 | ||
| 1034 | Tools::RenderdocAPI& System::GetRenderdocAPI() { | ||
| 1035 | return *impl->renderdoc_api; | ||
| 1036 | } | ||
| 1037 | |||
| 1012 | void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { | 1038 | void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { |
| 1013 | return impl->kernel.RunServer(std::move(server_manager)); | 1039 | return impl->kernel.RunServer(std::move(server_manager)); |
| 1014 | } | 1040 | } |
| @@ -1025,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) { | |||
| 1025 | } | 1051 | } |
| 1026 | } | 1052 | } |
| 1027 | 1053 | ||
| 1054 | std::deque<std::vector<u8>>& System::GetUserChannel() { | ||
| 1055 | return impl->user_channel; | ||
| 1056 | } | ||
| 1057 | |||
| 1028 | void System::RegisterExitCallback(ExitCallback&& callback) { | 1058 | void System::RegisterExitCallback(ExitCallback&& callback) { |
| 1029 | impl->exit_callback = std::move(callback); | 1059 | impl->exit_callback = std::move(callback); |
| 1030 | } | 1060 | } |
diff --git a/src/core/core.h b/src/core/core.h index c70ea1965..df20f26f3 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <cstddef> | 6 | #include <cstddef> |
| 7 | #include <deque> | ||
| 7 | #include <functional> | 8 | #include <functional> |
| 8 | #include <memory> | 9 | #include <memory> |
| 9 | #include <mutex> | 10 | #include <mutex> |
| @@ -101,6 +102,10 @@ namespace Network { | |||
| 101 | class RoomNetwork; | 102 | class RoomNetwork; |
| 102 | } | 103 | } |
| 103 | 104 | ||
| 105 | namespace Tools { | ||
| 106 | class RenderdocAPI; | ||
| 107 | } | ||
| 108 | |||
| 104 | namespace Core { | 109 | namespace Core { |
| 105 | 110 | ||
| 106 | class ARM_Interface; | 111 | class ARM_Interface; |
| @@ -412,8 +417,13 @@ public: | |||
| 412 | /// Gets an immutable reference to the Room Network. | 417 | /// Gets an immutable reference to the Room Network. |
| 413 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; | 418 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; |
| 414 | 419 | ||
| 415 | void SetExitLock(bool locked); | 420 | [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI(); |
| 416 | [[nodiscard]] bool GetExitLock() const; | 421 | |
| 422 | void SetExitLocked(bool locked); | ||
| 423 | bool GetExitLocked() const; | ||
| 424 | |||
| 425 | void SetExitRequested(bool requested); | ||
| 426 | bool GetExitRequested() const; | ||
| 417 | 427 | ||
| 418 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); | 428 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); |
| 419 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; | 429 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; |
| @@ -456,6 +466,12 @@ public: | |||
| 456 | */ | 466 | */ |
| 457 | void ExecuteProgram(std::size_t program_index); | 467 | void ExecuteProgram(std::size_t program_index); |
| 458 | 468 | ||
| 469 | /** | ||
| 470 | * Gets a reference to the user channel stack. | ||
| 471 | * It is used to transfer data between programs. | ||
| 472 | */ | ||
| 473 | [[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel(); | ||
| 474 | |||
| 459 | /// Type used for the frontend to designate a callback for System to exit the application. | 475 | /// Type used for the frontend to designate a callback for System to exit the application. |
| 460 | using ExitCallback = std::function<void()>; | 476 | using ExitCallback = std::function<void()>; |
| 461 | 477 | ||
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 4ff2c50e5..43a3c5ffd 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -35,7 +35,6 @@ namespace Core::Crypto { | |||
| 35 | namespace { | 35 | namespace { |
| 36 | 36 | ||
| 37 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; | 37 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; |
| 38 | constexpr u64 FULL_TICKET_SIZE = 0x400; | ||
| 39 | 38 | ||
| 40 | using Common::AsArray; | 39 | using Common::AsArray; |
| 41 | 40 | ||
| @@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { | |||
| 156 | UNREACHABLE(); | 155 | UNREACHABLE(); |
| 157 | } | 156 | } |
| 158 | 157 | ||
| 158 | bool Ticket::IsValid() const { | ||
| 159 | return !std::holds_alternative<std::monostate>(data); | ||
| 160 | } | ||
| 161 | |||
| 159 | SignatureType Ticket::GetSignatureType() const { | 162 | SignatureType Ticket::GetSignatureType() const { |
| 160 | if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { | 163 | if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { |
| 161 | return ticket->sig_type; | 164 | return ticket->sig_type; |
| @@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ | |||
| 210 | return Ticket{out}; | 213 | return Ticket{out}; |
| 211 | } | 214 | } |
| 212 | 215 | ||
| 216 | Ticket Ticket::Read(const FileSys::VirtualFile& file) { | ||
| 217 | // Attempt to read up to the largest ticket size, and make sure we read at least a signature | ||
| 218 | // type. | ||
| 219 | std::array<u8, sizeof(RSA4096Ticket)> raw_data{}; | ||
| 220 | auto read_size = file->Read(raw_data.data(), raw_data.size(), 0); | ||
| 221 | if (read_size < sizeof(SignatureType)) { | ||
| 222 | LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size); | ||
| 223 | return Ticket{std::monostate()}; | ||
| 224 | } | ||
| 225 | return Read(std::span{raw_data}); | ||
| 226 | } | ||
| 227 | |||
| 228 | Ticket Ticket::Read(std::span<const u8> raw_data) { | ||
| 229 | // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so | ||
| 230 | // just make sure we have at least the bare minimum of data to work with. | ||
| 231 | SignatureType sig_type; | ||
| 232 | if (raw_data.size() < sizeof(SignatureType)) { | ||
| 233 | LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.", | ||
| 234 | raw_data.size()); | ||
| 235 | return Ticket{std::monostate()}; | ||
| 236 | } | ||
| 237 | std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type)); | ||
| 238 | |||
| 239 | switch (sig_type) { | ||
| 240 | case SignatureType::RSA_4096_SHA1: | ||
| 241 | case SignatureType::RSA_4096_SHA256: { | ||
| 242 | RSA4096Ticket ticket{}; | ||
| 243 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 244 | return Ticket{ticket}; | ||
| 245 | } | ||
| 246 | case SignatureType::RSA_2048_SHA1: | ||
| 247 | case SignatureType::RSA_2048_SHA256: { | ||
| 248 | RSA2048Ticket ticket{}; | ||
| 249 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 250 | return Ticket{ticket}; | ||
| 251 | } | ||
| 252 | case SignatureType::ECDSA_SHA1: | ||
| 253 | case SignatureType::ECDSA_SHA256: { | ||
| 254 | ECDSATicket ticket{}; | ||
| 255 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 256 | return Ticket{ticket}; | ||
| 257 | } | ||
| 258 | default: | ||
| 259 | LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type); | ||
| 260 | return Ticket{std::monostate()}; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 213 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { | 264 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { |
| 214 | Key128 out{}; | 265 | Key128 out{}; |
| 215 | 266 | ||
| @@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { | |||
| 290 | } | 341 | } |
| 291 | } | 342 | } |
| 292 | 343 | ||
| 293 | RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | 344 | void KeyManager::DeriveETicketRSAKey() { |
| 294 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { | 345 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { |
| 295 | return {}; | 346 | return; |
| 296 | } | 347 | } |
| 297 | 348 | ||
| 298 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); | 349 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); |
| @@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | |||
| 304 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, | 355 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, |
| 305 | extended_dec.data(), Op::Decrypt); | 356 | extended_dec.data(), Op::Decrypt); |
| 306 | 357 | ||
| 307 | RSAKeyPair<2048> rsa_key{}; | 358 | std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(), |
| 308 | std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); | 359 | eticket_rsa_keypair.decryption_key.size()); |
| 309 | std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); | 360 | std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100, |
| 310 | std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); | 361 | eticket_rsa_keypair.modulus.size()); |
| 311 | 362 | std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200, | |
| 312 | return rsa_key; | 363 | eticket_rsa_keypair.exponent.size()); |
| 313 | } | 364 | } |
| 314 | 365 | ||
| 315 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { | 366 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { |
| @@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { | |||
| 447 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { | 498 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { |
| 448 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && | 499 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && |
| 449 | buffer[offset + 3] == 0x0) { | 500 | buffer[offset + 3] == 0x0) { |
| 450 | out.emplace_back(); | 501 | // NOTE: Assumes ticket blob will only contain RSA-2048 tickets. |
| 451 | auto& next = out.back(); | 502 | auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)}); |
| 452 | std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); | 503 | offset += sizeof(RSA2048Ticket); |
| 453 | offset += FULL_TICKET_SIZE; | 504 | if (ticket.IsValid()) { |
| 505 | out.push_back(ticket); | ||
| 506 | } | ||
| 454 | } | 507 | } |
| 455 | } | 508 | } |
| 456 | 509 | ||
| @@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { | |||
| 503 | return offset; | 556 | return offset; |
| 504 | } | 557 | } |
| 505 | 558 | ||
| 506 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | 559 | std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) { |
| 507 | const RSAKeyPair<2048>& key) { | 560 | if (!ticket.IsValid()) { |
| 561 | LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket."); | ||
| 562 | return std::nullopt; | ||
| 563 | } | ||
| 564 | |||
| 565 | if (ticket.GetData().rights_id == Key128{}) { | ||
| 566 | LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID."); | ||
| 567 | return std::nullopt; | ||
| 568 | } | ||
| 569 | |||
| 508 | const auto issuer = ticket.GetData().issuer; | 570 | const auto issuer = ticket.GetData().issuer; |
| 509 | if (IsAllZeroArray(issuer)) { | 571 | if (IsAllZeroArray(issuer)) { |
| 572 | LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer."); | ||
| 510 | return std::nullopt; | 573 | return std::nullopt; |
| 511 | } | 574 | } |
| 575 | |||
| 512 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { | 576 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { |
| 513 | LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); | 577 | LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority."); |
| 514 | } | 578 | } |
| 515 | 579 | ||
| 516 | Key128 rights_id = ticket.GetData().rights_id; | 580 | if (ticket.GetData().type == TitleKeyType::Common) { |
| 517 | 581 | return ticket.GetData().title_key_common; | |
| 518 | if (rights_id == Key128{}) { | ||
| 519 | return std::nullopt; | ||
| 520 | } | 582 | } |
| 521 | 583 | ||
| 522 | if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), | 584 | if (eticket_rsa_keypair == RSAKeyPair<2048>{}) { |
| 523 | ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { | 585 | LOG_WARNING( |
| 524 | return std::make_pair(rights_id, ticket.GetData().title_key_common); | 586 | Crypto, |
| 587 | "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair."); | ||
| 588 | return std::nullopt; | ||
| 525 | } | 589 | } |
| 526 | 590 | ||
| 527 | mbedtls_mpi D; // RSA Private Exponent | 591 | mbedtls_mpi D; // RSA Private Exponent |
| @@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 534 | mbedtls_mpi_init(&S); | 598 | mbedtls_mpi_init(&S); |
| 535 | mbedtls_mpi_init(&M); | 599 | mbedtls_mpi_init(&M); |
| 536 | 600 | ||
| 537 | mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); | 601 | const auto& title_key_block = ticket.GetData().title_key_block; |
| 538 | mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); | 602 | mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(), |
| 539 | mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); | 603 | eticket_rsa_keypair.decryption_key.size()); |
| 604 | mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(), | ||
| 605 | eticket_rsa_keypair.modulus.size()); | ||
| 606 | mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size()); | ||
| 540 | 607 | ||
| 541 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); | 608 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); |
| 542 | 609 | ||
| @@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 564 | 631 | ||
| 565 | Key128 key_temp{}; | 632 | Key128 key_temp{}; |
| 566 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); | 633 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); |
| 567 | 634 | return key_temp; | |
| 568 | return std::make_pair(rights_id, key_temp); | ||
| 569 | } | 635 | } |
| 570 | 636 | ||
| 571 | KeyManager::KeyManager() { | 637 | KeyManager::KeyManager() { |
| @@ -658,17 +724,25 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 658 | continue; | 724 | continue; |
| 659 | } | 725 | } |
| 660 | 726 | ||
| 661 | const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); | 727 | const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16); |
| 662 | keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); | 728 | keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); |
| 663 | } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { | 729 | } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { |
| 664 | if (!ValidCryptoRevisionString(out[0], 18, 2)) { | 730 | if (!ValidCryptoRevisionString(out[0], 18, 2)) { |
| 665 | continue; | 731 | continue; |
| 666 | } | 732 | } |
| 667 | 733 | ||
| 668 | const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); | 734 | const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16); |
| 669 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); | 735 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); |
| 670 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { | 736 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { |
| 671 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); | 737 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); |
| 738 | } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) { | ||
| 739 | const auto key_data = Common::HexStringToArray<528>(out[1]); | ||
| 740 | std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(), | ||
| 741 | eticket_rsa_keypair.decryption_key.size()); | ||
| 742 | std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100, | ||
| 743 | eticket_rsa_keypair.modulus.size()); | ||
| 744 | std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200, | ||
| 745 | eticket_rsa_keypair.exponent.size()); | ||
| 672 | } else { | 746 | } else { |
| 673 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { | 747 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { |
| 674 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { | 748 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { |
| @@ -676,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 676 | } | 750 | } |
| 677 | if (out[0].compare(0, kv.second.size(), kv.second) == 0) { | 751 | if (out[0].compare(0, kv.second.size(), kv.second) == 0) { |
| 678 | const auto index = | 752 | const auto index = |
| 679 | std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); | 753 | std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16); |
| 680 | const auto sub = kv.first.second; | 754 | const auto sub = kv.first.second; |
| 681 | if (sub == 0) { | 755 | if (sub == 0) { |
| 682 | s128_keys[{kv.first.first, index, 0}] = | 756 | s128_keys[{kv.first.first, index, 0}] = |
| @@ -696,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 696 | const auto& match = kak_names[j]; | 770 | const auto& match = kak_names[j]; |
| 697 | if (out[0].compare(0, std::strlen(match), match) == 0) { | 771 | if (out[0].compare(0, std::strlen(match), match) == 0) { |
| 698 | const auto index = | 772 | const auto index = |
| 699 | std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); | 773 | std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16); |
| 700 | s128_keys[{S128KeyType::KeyArea, index, j}] = | 774 | s128_keys[{S128KeyType::KeyArea, index, j}] = |
| 701 | Common::HexStringToArray<16>(out[1]); | 775 | Common::HexStringToArray<16>(out[1]); |
| 702 | } | 776 | } |
| @@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data, | |||
| 1110 | 1184 | ||
| 1111 | eticket_extended_kek = data.GetETicketExtendedKek(); | 1185 | eticket_extended_kek = data.GetETicketExtendedKek(); |
| 1112 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); | 1186 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); |
| 1187 | DeriveETicketRSAKey(); | ||
| 1113 | PopulateTickets(); | 1188 | PopulateTickets(); |
| 1114 | } | 1189 | } |
| 1115 | 1190 | ||
| 1116 | void KeyManager::PopulateTickets() { | 1191 | void KeyManager::PopulateTickets() { |
| 1117 | const auto rsa_key = GetETicketRSAKey(); | 1192 | if (ticket_databases_loaded) { |
| 1118 | |||
| 1119 | if (rsa_key == RSAKeyPair<2048>{}) { | ||
| 1120 | return; | 1193 | return; |
| 1121 | } | 1194 | } |
| 1195 | ticket_databases_loaded = true; | ||
| 1122 | 1196 | ||
| 1123 | if (!common_tickets.empty() && !personal_tickets.empty()) { | 1197 | std::vector<Ticket> tickets; |
| 1124 | return; | ||
| 1125 | } | ||
| 1126 | 1198 | ||
| 1127 | const auto system_save_e1_path = | 1199 | const auto system_save_e1_path = |
| 1128 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; | 1200 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; |
| 1129 | 1201 | if (Common::FS::Exists(system_save_e1_path)) { | |
| 1130 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, | 1202 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, |
| 1131 | Common::FS::FileType::BinaryFile}; | 1203 | Common::FS::FileType::BinaryFile}; |
| 1204 | const auto blob1 = GetTicketblob(save_e1); | ||
| 1205 | tickets.insert(tickets.end(), blob1.begin(), blob1.end()); | ||
| 1206 | } | ||
| 1132 | 1207 | ||
| 1133 | const auto system_save_e2_path = | 1208 | const auto system_save_e2_path = |
| 1134 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; | 1209 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; |
| 1210 | if (Common::FS::Exists(system_save_e2_path)) { | ||
| 1211 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | ||
| 1212 | Common::FS::FileType::BinaryFile}; | ||
| 1213 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1214 | tickets.insert(tickets.end(), blob2.begin(), blob2.end()); | ||
| 1215 | } | ||
| 1135 | 1216 | ||
| 1136 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | 1217 | for (const auto& ticket : tickets) { |
| 1137 | Common::FS::FileType::BinaryFile}; | 1218 | AddTicket(ticket); |
| 1138 | |||
| 1139 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1140 | auto res = GetTicketblob(save_e1); | ||
| 1141 | |||
| 1142 | const auto idx = res.size(); | ||
| 1143 | res.insert(res.end(), blob2.begin(), blob2.end()); | ||
| 1144 | |||
| 1145 | for (std::size_t i = 0; i < res.size(); ++i) { | ||
| 1146 | const auto common = i < idx; | ||
| 1147 | const auto pair = ParseTicket(res[i], rsa_key); | ||
| 1148 | if (!pair) { | ||
| 1149 | continue; | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | const auto& [rid, key] = *pair; | ||
| 1153 | u128 rights_id; | ||
| 1154 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1155 | |||
| 1156 | if (common) { | ||
| 1157 | common_tickets[rights_id] = res[i]; | ||
| 1158 | } else { | ||
| 1159 | personal_tickets[rights_id] = res[i]; | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1163 | } | 1219 | } |
| 1164 | } | 1220 | } |
| 1165 | 1221 | ||
| @@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { | |||
| 1291 | return personal_tickets; | 1347 | return personal_tickets; |
| 1292 | } | 1348 | } |
| 1293 | 1349 | ||
| 1294 | bool KeyManager::AddTicketCommon(Ticket raw) { | 1350 | bool KeyManager::AddTicket(const Ticket& ticket) { |
| 1295 | const auto rsa_key = GetETicketRSAKey(); | 1351 | if (!ticket.IsValid()) { |
| 1296 | if (rsa_key == RSAKeyPair<2048>{}) { | 1352 | LOG_WARNING(Crypto, "Attempted to add invalid ticket."); |
| 1297 | return false; | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | const auto pair = ParseTicket(raw, rsa_key); | ||
| 1301 | if (!pair) { | ||
| 1302 | return false; | 1353 | return false; |
| 1303 | } | 1354 | } |
| 1304 | 1355 | ||
| 1305 | const auto& [rid, key] = *pair; | 1356 | const auto& rid = ticket.GetData().rights_id; |
| 1306 | u128 rights_id; | 1357 | u128 rights_id; |
| 1307 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | 1358 | std::memcpy(rights_id.data(), rid.data(), rid.size()); |
| 1308 | common_tickets[rights_id] = raw; | 1359 | if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) { |
| 1309 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | 1360 | common_tickets[rights_id] = ticket; |
| 1310 | return true; | 1361 | } else { |
| 1311 | } | 1362 | personal_tickets[rights_id] = ticket; |
| 1363 | } | ||
| 1312 | 1364 | ||
| 1313 | bool KeyManager::AddTicketPersonalized(Ticket raw) { | 1365 | if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) { |
| 1314 | const auto rsa_key = GetETicketRSAKey(); | 1366 | LOG_DEBUG(Crypto, |
| 1315 | if (rsa_key == RSAKeyPair<2048>{}) { | 1367 | "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.", |
| 1316 | return false; | 1368 | rights_id[1], rights_id[0]); |
| 1369 | return true; | ||
| 1317 | } | 1370 | } |
| 1318 | 1371 | ||
| 1319 | const auto pair = ParseTicket(raw, rsa_key); | 1372 | const auto key = ParseTicketTitleKey(ticket); |
| 1320 | if (!pair) { | 1373 | if (!key) { |
| 1321 | return false; | 1374 | return false; |
| 1322 | } | 1375 | } |
| 1323 | 1376 | SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]); | |
| 1324 | const auto& [rid, key] = *pair; | ||
| 1325 | u128 rights_id; | ||
| 1326 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1327 | common_tickets[rights_id] = raw; | ||
| 1328 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1329 | return true; | 1377 | return true; |
| 1330 | } | 1378 | } |
| 1331 | } // namespace Core::Crypto | 1379 | } // namespace Core::Crypto |
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 8c864503b..2250eccec 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <filesystem> | 7 | #include <filesystem> |
| 8 | #include <map> | 8 | #include <map> |
| 9 | #include <optional> | 9 | #include <optional> |
| 10 | #include <span> | ||
| 10 | #include <string> | 11 | #include <string> |
| 11 | 12 | ||
| 12 | #include <variant> | 13 | #include <variant> |
| @@ -29,8 +30,6 @@ enum class ResultStatus : u16; | |||
| 29 | 30 | ||
| 30 | namespace Core::Crypto { | 31 | namespace Core::Crypto { |
| 31 | 32 | ||
| 32 | constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | ||
| 33 | |||
| 34 | using Key128 = std::array<u8, 0x10>; | 33 | using Key128 = std::array<u8, 0x10>; |
| 35 | using Key256 = std::array<u8, 0x20>; | 34 | using Key256 = std::array<u8, 0x20>; |
| 36 | using SHA256Hash = std::array<u8, 0x20>; | 35 | using SHA256Hash = std::array<u8, 0x20>; |
| @@ -82,6 +81,7 @@ struct RSA4096Ticket { | |||
| 82 | INSERT_PADDING_BYTES(0x3C); | 81 | INSERT_PADDING_BYTES(0x3C); |
| 83 | TicketData data; | 82 | TicketData data; |
| 84 | }; | 83 | }; |
| 84 | static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size."); | ||
| 85 | 85 | ||
| 86 | struct RSA2048Ticket { | 86 | struct RSA2048Ticket { |
| 87 | SignatureType sig_type; | 87 | SignatureType sig_type; |
| @@ -89,6 +89,7 @@ struct RSA2048Ticket { | |||
| 89 | INSERT_PADDING_BYTES(0x3C); | 89 | INSERT_PADDING_BYTES(0x3C); |
| 90 | TicketData data; | 90 | TicketData data; |
| 91 | }; | 91 | }; |
| 92 | static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size."); | ||
| 92 | 93 | ||
| 93 | struct ECDSATicket { | 94 | struct ECDSATicket { |
| 94 | SignatureType sig_type; | 95 | SignatureType sig_type; |
| @@ -96,16 +97,41 @@ struct ECDSATicket { | |||
| 96 | INSERT_PADDING_BYTES(0x40); | 97 | INSERT_PADDING_BYTES(0x40); |
| 97 | TicketData data; | 98 | TicketData data; |
| 98 | }; | 99 | }; |
| 100 | static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size."); | ||
| 99 | 101 | ||
| 100 | struct Ticket { | 102 | struct Ticket { |
| 101 | std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; | 103 | std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; |
| 102 | 104 | ||
| 103 | SignatureType GetSignatureType() const; | 105 | [[nodiscard]] bool IsValid() const; |
| 104 | TicketData& GetData(); | 106 | [[nodiscard]] SignatureType GetSignatureType() const; |
| 105 | const TicketData& GetData() const; | 107 | [[nodiscard]] TicketData& GetData(); |
| 106 | u64 GetSize() const; | 108 | [[nodiscard]] const TicketData& GetData() const; |
| 107 | 109 | [[nodiscard]] u64 GetSize() const; | |
| 110 | |||
| 111 | /** | ||
| 112 | * Synthesizes a common ticket given a title key and rights ID. | ||
| 113 | * | ||
| 114 | * @param title_key Title key to store in the ticket. | ||
| 115 | * @param rights_id Rights ID the ticket is for. | ||
| 116 | * @return The synthesized common ticket. | ||
| 117 | */ | ||
| 108 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); | 118 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); |
| 119 | |||
| 120 | /** | ||
| 121 | * Reads a ticket from a file. | ||
| 122 | * | ||
| 123 | * @param file File to read the ticket from. | ||
| 124 | * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. | ||
| 125 | */ | ||
| 126 | static Ticket Read(const FileSys::VirtualFile& file); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Reads a ticket from a memory buffer. | ||
| 130 | * | ||
| 131 | * @param raw_data Buffer to read the ticket from. | ||
| 132 | * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. | ||
| 133 | */ | ||
| 134 | static Ticket Read(std::span<const u8> raw_data); | ||
| 109 | }; | 135 | }; |
| 110 | 136 | ||
| 111 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | 137 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
| @@ -264,8 +290,7 @@ public: | |||
| 264 | const std::map<u128, Ticket>& GetCommonTickets() const; | 290 | const std::map<u128, Ticket>& GetCommonTickets() const; |
| 265 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; | 291 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; |
| 266 | 292 | ||
| 267 | bool AddTicketCommon(Ticket raw); | 293 | bool AddTicket(const Ticket& ticket); |
| 268 | bool AddTicketPersonalized(Ticket raw); | ||
| 269 | 294 | ||
| 270 | void ReloadKeys(); | 295 | void ReloadKeys(); |
| 271 | bool AreKeysLoaded() const; | 296 | bool AreKeysLoaded() const; |
| @@ -279,10 +304,12 @@ private: | |||
| 279 | // Map from rights ID to ticket | 304 | // Map from rights ID to ticket |
| 280 | std::map<u128, Ticket> common_tickets; | 305 | std::map<u128, Ticket> common_tickets; |
| 281 | std::map<u128, Ticket> personal_tickets; | 306 | std::map<u128, Ticket> personal_tickets; |
| 307 | bool ticket_databases_loaded = false; | ||
| 282 | 308 | ||
| 283 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; | 309 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; |
| 284 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; | 310 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; |
| 285 | std::array<u8, 576> eticket_extended_kek{}; | 311 | std::array<u8, 576> eticket_extended_kek{}; |
| 312 | RSAKeyPair<2048> eticket_rsa_keypair{}; | ||
| 286 | 313 | ||
| 287 | bool dev_mode; | 314 | bool dev_mode; |
| 288 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); | 315 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); |
| @@ -293,10 +320,13 @@ private: | |||
| 293 | 320 | ||
| 294 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); | 321 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); |
| 295 | 322 | ||
| 296 | RSAKeyPair<2048> GetETicketRSAKey() const; | 323 | void DeriveETicketRSAKey(); |
| 297 | 324 | ||
| 298 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); | 325 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); |
| 299 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); | 326 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); |
| 327 | |||
| 328 | /// Parses the title key section of a ticket. | ||
| 329 | std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket); | ||
| 300 | }; | 330 | }; |
| 301 | 331 | ||
| 302 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); | 332 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); |
| @@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke | |||
| 311 | 341 | ||
| 312 | std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); | 342 | std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); |
| 313 | 343 | ||
| 314 | // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority | ||
| 315 | // (offset 0x140-0x144 is zero) | ||
| 316 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | ||
| 317 | const RSAKeyPair<2048>& eticket_extended_key); | ||
| 318 | |||
| 319 | } // namespace Core::Crypto | 344 | } // namespace Core::Crypto |
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 0f839d5b4..e55831f27 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 263 | 263 | ||
| 264 | std::vector<u8> mem(size); | 264 | std::vector<u8> mem(size); |
| 265 | if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { | 265 | if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { |
| 266 | // Restore any bytes belonging to replaced instructions. | ||
| 267 | auto it = replaced_instructions.lower_bound(addr); | ||
| 268 | for (; it != replaced_instructions.end() && it->first < addr + size; it++) { | ||
| 269 | // Get the bytes of the instruction we previously replaced. | ||
| 270 | const u32 original_bytes = it->second; | ||
| 271 | |||
| 272 | // Calculate where to start writing to the output buffer. | ||
| 273 | const size_t output_offset = it->first - addr; | ||
| 274 | |||
| 275 | // Calculate how many bytes to write. | ||
| 276 | // The loop condition ensures output_offset < size. | ||
| 277 | const size_t n = std::min<size_t>(size - output_offset, sizeof(u32)); | ||
| 278 | |||
| 279 | // Write the bytes to the output buffer. | ||
| 280 | std::memcpy(mem.data() + output_offset, &original_bytes, n); | ||
| 281 | } | ||
| 282 | |||
| 266 | SendReply(Common::HexToString(mem)); | 283 | SendReply(Common::HexToString(mem)); |
| 267 | } else { | 284 | } else { |
| 268 | SendReply(GDB_STUB_REPLY_ERR); | 285 | SendReply(GDB_STUB_REPLY_ERR); |
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 5d02865f4..8b9a4fc5a 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp | |||
| @@ -31,13 +31,9 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) | |||
| 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, | 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, |
| 32 | partitions(partition_names.size()), | 32 | partitions(partition_names.size()), |
| 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { | 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { |
| 34 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | 34 | const auto header_status = TryReadHeader(); |
| 35 | status = Loader::ResultStatus::ErrorBadXCIHeader; | 35 | if (header_status != Loader::ResultStatus::Success) { |
| 36 | return; | 36 | status = header_status; |
| 37 | } | ||
| 38 | |||
| 39 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 40 | status = Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 41 | return; | 37 | return; |
| 42 | } | 38 | } |
| 43 | 39 | ||
| @@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() { | |||
| 183 | } | 179 | } |
| 184 | 180 | ||
| 185 | for (const auto& update_file : update->GetFiles()) { | 181 | for (const auto& update_file : update->GetFiles()) { |
| 186 | NCA nca{update_file, nullptr, 0}; | 182 | NCA nca{update_file}; |
| 187 | 183 | ||
| 188 | if (nca.GetStatus() != Loader::ResultStatus::Success) { | 184 | if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) { |
| 189 | continue; | 185 | continue; |
| 190 | } | 186 | } |
| 191 | 187 | ||
| @@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 296 | continue; | 292 | continue; |
| 297 | } | 293 | } |
| 298 | 294 | ||
| 299 | auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); | 295 | auto nca = std::make_shared<NCA>(partition_file); |
| 300 | if (nca->IsUpdate()) { | 296 | if (nca->IsUpdate()) { |
| 301 | continue; | 297 | continue; |
| 302 | } | 298 | } |
| @@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 316 | return Loader::ResultStatus::Success; | 312 | return Loader::ResultStatus::Success; |
| 317 | } | 313 | } |
| 318 | 314 | ||
| 315 | Loader::ResultStatus XCI::TryReadHeader() { | ||
| 316 | constexpr size_t CardInitialDataRegionSize = 0x1000; | ||
| 317 | |||
| 318 | // Define the function we'll use to determine if we read a valid header. | ||
| 319 | const auto ReadCardHeader = [&]() { | ||
| 320 | // Ensure we can read the entire header. If we can't, we can't read the card image. | ||
| 321 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | ||
| 322 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 323 | } | ||
| 324 | |||
| 325 | // Ensure the header magic matches. If it doesn't, this isn't a card image header. | ||
| 326 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 327 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 328 | } | ||
| 329 | |||
| 330 | // We read a card image header. | ||
| 331 | return Loader::ResultStatus::Success; | ||
| 332 | }; | ||
| 333 | |||
| 334 | // Try to read the header directly. | ||
| 335 | if (ReadCardHeader() == Loader::ResultStatus::Success) { | ||
| 336 | return Loader::ResultStatus::Success; | ||
| 337 | } | ||
| 338 | |||
| 339 | // Get the size of the file. | ||
| 340 | const size_t card_image_size = file->GetSize(); | ||
| 341 | |||
| 342 | // If we are large enough to have a key area, offset past the key area and retry. | ||
| 343 | if (card_image_size >= CardInitialDataRegionSize) { | ||
| 344 | file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize, | ||
| 345 | CardInitialDataRegionSize); | ||
| 346 | return ReadCardHeader(); | ||
| 347 | } | ||
| 348 | |||
| 349 | // We had no header and aren't large enough to have a key area, so this can't be parsed. | ||
| 350 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 351 | } | ||
| 352 | |||
| 319 | u8 XCI::GetFormatVersion() { | 353 | u8 XCI::GetFormatVersion() { |
| 320 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; | 354 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; |
| 321 | } | 355 | } |
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 1283f8216..9886123e7 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h | |||
| @@ -128,6 +128,7 @@ public: | |||
| 128 | 128 | ||
| 129 | private: | 129 | private: |
| 130 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); | 130 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); |
| 131 | Loader::ResultStatus TryReadHeader(); | ||
| 131 | 132 | ||
| 132 | VirtualFile file; | 133 | VirtualFile file; |
| 133 | GamecardHeader header{}; | 134 | GamecardHeader header{}; |
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 06efab46d..7d2f0abb8 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -12,545 +12,118 @@ | |||
| 12 | #include "core/crypto/ctr_encryption_layer.h" | 12 | #include "core/crypto/ctr_encryption_layer.h" |
| 13 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 14 | #include "core/file_sys/content_archive.h" | 14 | #include "core/file_sys/content_archive.h" |
| 15 | #include "core/file_sys/nca_patch.h" | ||
| 16 | #include "core/file_sys/partition_filesystem.h" | 15 | #include "core/file_sys/partition_filesystem.h" |
| 17 | #include "core/file_sys/vfs_offset.h" | 16 | #include "core/file_sys/vfs_offset.h" |
| 18 | #include "core/loader/loader.h" | 17 | #include "core/loader/loader.h" |
| 19 | 18 | ||
| 19 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" | ||
| 20 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 21 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 22 | |||
| 20 | namespace FileSys { | 23 | namespace FileSys { |
| 21 | 24 | ||
| 22 | // Media offsets in headers are stored divided by 512. Mult. by this to get real offset. | 25 | static u8 MasterKeyIdForKeyGeneration(u8 key_generation) { |
| 23 | constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; | 26 | return std::max<u8>(key_generation, 1) - 1; |
| 24 | |||
| 25 | constexpr u64 SECTION_HEADER_SIZE = 0x200; | ||
| 26 | constexpr u64 SECTION_HEADER_OFFSET = 0x400; | ||
| 27 | |||
| 28 | constexpr u32 IVFC_MAX_LEVEL = 6; | ||
| 29 | |||
| 30 | enum class NCASectionFilesystemType : u8 { | ||
| 31 | PFS0 = 0x2, | ||
| 32 | ROMFS = 0x3, | ||
| 33 | }; | ||
| 34 | |||
| 35 | struct IVFCLevel { | ||
| 36 | u64_le offset; | ||
| 37 | u64_le size; | ||
| 38 | u32_le block_size; | ||
| 39 | u32_le reserved; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); | ||
| 42 | |||
| 43 | struct IVFCHeader { | ||
| 44 | u32_le magic; | ||
| 45 | u32_le magic_number; | ||
| 46 | INSERT_PADDING_BYTES_NOINIT(8); | ||
| 47 | std::array<IVFCLevel, 6> levels; | ||
| 48 | INSERT_PADDING_BYTES_NOINIT(64); | ||
| 49 | }; | ||
| 50 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | ||
| 51 | |||
| 52 | struct NCASectionHeaderBlock { | ||
| 53 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 54 | NCASectionFilesystemType filesystem_type; | ||
| 55 | NCASectionCryptoType crypto_type; | ||
| 56 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 57 | }; | ||
| 58 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | ||
| 59 | |||
| 60 | struct NCABucketInfo { | ||
| 61 | u64 table_offset; | ||
| 62 | u64 table_size; | ||
| 63 | std::array<u8, 0x10> table_header; | ||
| 64 | }; | ||
| 65 | static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size."); | ||
| 66 | |||
| 67 | struct NCASparseInfo { | ||
| 68 | NCABucketInfo bucket; | ||
| 69 | u64 physical_offset; | ||
| 70 | u16 generation; | ||
| 71 | INSERT_PADDING_BYTES_NOINIT(0x6); | ||
| 72 | }; | ||
| 73 | static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size."); | ||
| 74 | |||
| 75 | struct NCACompressionInfo { | ||
| 76 | NCABucketInfo bucket; | ||
| 77 | INSERT_PADDING_BYTES_NOINIT(0x8); | ||
| 78 | }; | ||
| 79 | static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size."); | ||
| 80 | |||
| 81 | struct NCASectionRaw { | ||
| 82 | NCASectionHeaderBlock header; | ||
| 83 | std::array<u8, 0x138> block_data; | ||
| 84 | std::array<u8, 0x8> section_ctr; | ||
| 85 | NCASparseInfo sparse_info; | ||
| 86 | NCACompressionInfo compression_info; | ||
| 87 | INSERT_PADDING_BYTES_NOINIT(0x60); | ||
| 88 | }; | ||
| 89 | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||
| 90 | |||
| 91 | struct PFS0Superblock { | ||
| 92 | NCASectionHeaderBlock header_block; | ||
| 93 | std::array<u8, 0x20> hash; | ||
| 94 | u32_le size; | ||
| 95 | INSERT_PADDING_BYTES_NOINIT(4); | ||
| 96 | u64_le hash_table_offset; | ||
| 97 | u64_le hash_table_size; | ||
| 98 | u64_le pfs0_header_offset; | ||
| 99 | u64_le pfs0_size; | ||
| 100 | INSERT_PADDING_BYTES_NOINIT(0x1B0); | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | ||
| 103 | |||
| 104 | struct RomFSSuperblock { | ||
| 105 | NCASectionHeaderBlock header_block; | ||
| 106 | IVFCHeader ivfc; | ||
| 107 | INSERT_PADDING_BYTES_NOINIT(0x118); | ||
| 108 | }; | ||
| 109 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); | ||
| 110 | |||
| 111 | struct BKTRHeader { | ||
| 112 | u64_le offset; | ||
| 113 | u64_le size; | ||
| 114 | u32_le magic; | ||
| 115 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 116 | u32_le number_entries; | ||
| 117 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 118 | }; | ||
| 119 | static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); | ||
| 120 | |||
| 121 | struct BKTRSuperblock { | ||
| 122 | NCASectionHeaderBlock header_block; | ||
| 123 | IVFCHeader ivfc; | ||
| 124 | INSERT_PADDING_BYTES_NOINIT(0x18); | ||
| 125 | BKTRHeader relocation; | ||
| 126 | BKTRHeader subsection; | ||
| 127 | INSERT_PADDING_BYTES_NOINIT(0xC0); | ||
| 128 | }; | ||
| 129 | static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); | ||
| 130 | |||
| 131 | union NCASectionHeader { | ||
| 132 | NCASectionRaw raw{}; | ||
| 133 | PFS0Superblock pfs0; | ||
| 134 | RomFSSuperblock romfs; | ||
| 135 | BKTRSuperblock bktr; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||
| 138 | |||
| 139 | static bool IsValidNCA(const NCAHeader& header) { | ||
| 140 | // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||
| 141 | return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 142 | } | 27 | } |
| 143 | 28 | ||
| 144 | NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) | 29 | NCA::NCA(VirtualFile file_, const NCA* base_nca) |
| 145 | : file(std::move(file_)), | 30 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { |
| 146 | bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} { | ||
| 147 | if (file == nullptr) { | 31 | if (file == nullptr) { |
| 148 | status = Loader::ResultStatus::ErrorNullFile; | 32 | status = Loader::ResultStatus::ErrorNullFile; |
| 149 | return; | 33 | return; |
| 150 | } | 34 | } |
| 151 | 35 | ||
| 152 | if (sizeof(NCAHeader) != file->ReadObject(&header)) { | 36 | reader = std::make_shared<NcaReader>(); |
| 153 | LOG_ERROR(Loader, "File reader errored out during header read."); | 37 | if (Result rc = |
| 38 | reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration()); | ||
| 39 | R_FAILED(rc)) { | ||
| 40 | if (rc != ResultInvalidNcaSignature) { | ||
| 41 | LOG_ERROR(Loader, "File reader errored out during header read: {:#x}", | ||
| 42 | rc.GetInnerValue()); | ||
| 43 | } | ||
| 154 | status = Loader::ResultStatus::ErrorBadNCAHeader; | 44 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 155 | return; | 45 | return; |
| 156 | } | 46 | } |
| 157 | 47 | ||
| 158 | if (!HandlePotentialHeaderDecryption()) { | 48 | // Ensure we have the proper key area keys to continue. |
| 159 | return; | 49 | const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration()); |
| 160 | } | 50 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) { |
| 161 | 51 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | |
| 162 | has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; }); | ||
| 163 | |||
| 164 | const std::vector<NCASectionHeader> sections = ReadSectionHeaders(); | ||
| 165 | is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) { | ||
| 166 | return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR; | ||
| 167 | }); | ||
| 168 | |||
| 169 | if (!ReadSections(sections, bktr_base_ivfc_offset)) { | ||
| 170 | return; | 52 | return; |
| 171 | } | 53 | } |
| 172 | 54 | ||
| 173 | status = Loader::ResultStatus::Success; | 55 | RightsId rights_id{}; |
| 174 | } | 56 | reader->GetRightsId(rights_id.data(), rights_id.size()); |
| 175 | 57 | if (rights_id != RightsId{}) { | |
| 176 | NCA::~NCA() = default; | 58 | // External decryption key required; provide it here. |
| 177 | 59 | u128 rights_id_u128; | |
| 178 | bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { | 60 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); |
| 179 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { | ||
| 180 | status = Loader::ResultStatus::ErrorNCA2; | ||
| 181 | return false; | ||
| 182 | } | ||
| 183 | 61 | ||
| 184 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { | 62 | auto titlekey = |
| 185 | status = Loader::ResultStatus::ErrorNCA0; | 63 | keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]); |
| 186 | return false; | 64 | if (titlekey == Core::Crypto::Key128{}) { |
| 187 | } | 65 | status = Loader::ResultStatus::ErrorMissingTitlekey; |
| 188 | 66 | return; | |
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | bool NCA::HandlePotentialHeaderDecryption() { | ||
| 193 | if (IsValidNCA(header)) { | ||
| 194 | return true; | ||
| 195 | } | ||
| 196 | |||
| 197 | if (!CheckSupportedNCA(header)) { | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | |||
| 201 | NCAHeader dec_header{}; | ||
| 202 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 203 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 204 | cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | ||
| 205 | Core::Crypto::Op::Decrypt); | ||
| 206 | if (IsValidNCA(dec_header)) { | ||
| 207 | header = dec_header; | ||
| 208 | encrypted = true; | ||
| 209 | } else { | ||
| 210 | if (!CheckSupportedNCA(dec_header)) { | ||
| 211 | return false; | ||
| 212 | } | 67 | } |
| 213 | 68 | ||
| 214 | if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { | 69 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { |
| 215 | status = Loader::ResultStatus::ErrorIncorrectHeaderKey; | 70 | status = Loader::ResultStatus::ErrorMissingTitlekek; |
| 216 | } else { | 71 | return; |
| 217 | status = Loader::ResultStatus::ErrorMissingHeaderKey; | ||
| 218 | } | 72 | } |
| 219 | return false; | ||
| 220 | } | ||
| 221 | 73 | ||
| 222 | return true; | 74 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id); |
| 223 | } | 75 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); |
| 76 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), | ||
| 77 | Core::Crypto::Op::Decrypt); | ||
| 224 | 78 | ||
| 225 | std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { | 79 | reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size()); |
| 226 | const std::ptrdiff_t number_sections = | ||
| 227 | std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) { | ||
| 228 | return entry.media_offset > 0; | ||
| 229 | }); | ||
| 230 | |||
| 231 | std::vector<NCASectionHeader> sections(number_sections); | ||
| 232 | const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||
| 233 | |||
| 234 | if (encrypted) { | ||
| 235 | auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||
| 236 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 237 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 238 | cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||
| 239 | Core::Crypto::Op::Decrypt); | ||
| 240 | } else { | ||
| 241 | file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||
| 242 | } | ||
| 243 | |||
| 244 | return sections; | ||
| 245 | } | ||
| 246 | |||
| 247 | bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) { | ||
| 248 | for (std::size_t i = 0; i < sections.size(); ++i) { | ||
| 249 | const auto& section = sections[i]; | ||
| 250 | |||
| 251 | if (section.raw.sparse_info.bucket.table_offset != 0 && | ||
| 252 | section.raw.sparse_info.bucket.table_size != 0) { | ||
| 253 | LOG_ERROR(Loader, "Sparse NCAs are not supported."); | ||
| 254 | status = Loader::ResultStatus::ErrorSparseNCA; | ||
| 255 | return false; | ||
| 256 | } | ||
| 257 | |||
| 258 | if (section.raw.compression_info.bucket.table_offset != 0 && | ||
| 259 | section.raw.compression_info.bucket.table_size != 0) { | ||
| 260 | LOG_ERROR(Loader, "Compressed NCAs are not supported."); | ||
| 261 | status = Loader::ResultStatus::ErrorCompressedNCA; | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 266 | if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { | ||
| 267 | return false; | ||
| 268 | } | ||
| 269 | } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||
| 270 | if (!ReadPFS0Section(section, header.section_tables[i])) { | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | return true; | ||
| 277 | } | ||
| 278 | |||
| 279 | bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 280 | u64 bktr_base_ivfc_offset) { | ||
| 281 | const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; | ||
| 282 | ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 283 | const std::size_t romfs_offset = base_offset + ivfc_offset; | ||
| 284 | const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | ||
| 285 | auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); | ||
| 286 | auto dec = Decrypt(section, raw, romfs_offset); | ||
| 287 | |||
| 288 | if (dec == nullptr) { | ||
| 289 | if (status != Loader::ResultStatus::Success) | ||
| 290 | return false; | ||
| 291 | if (has_rights_id) | ||
| 292 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 293 | else | ||
| 294 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 295 | return false; | ||
| 296 | } | 80 | } |
| 297 | 81 | ||
| 298 | if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { | 82 | const s32 fs_count = reader->GetFsCount(); |
| 299 | if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || | 83 | NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader); |
| 300 | section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { | 84 | std::vector<VirtualFile> filesystems(fs_count); |
| 301 | status = Loader::ResultStatus::ErrorBadBKTRHeader; | 85 | for (s32 i = 0; i < fs_count; i++) { |
| 302 | return false; | 86 | NcaFsHeaderReader header_reader; |
| 303 | } | 87 | const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i); |
| 304 | 88 | if (R_FAILED(rc)) { | |
| 305 | if (section.bktr.relocation.offset + section.bktr.relocation.size != | 89 | LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i, |
| 306 | section.bktr.subsection.offset) { | 90 | rc.GetInnerValue()); |
| 307 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; | 91 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 308 | return false; | 92 | return; |
| 309 | } | ||
| 310 | |||
| 311 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 312 | if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { | ||
| 313 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; | ||
| 314 | return false; | ||
| 315 | } | ||
| 316 | |||
| 317 | const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 318 | RelocationBlock relocation_block{}; | ||
| 319 | if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != | ||
| 320 | sizeof(RelocationBlock)) { | ||
| 321 | status = Loader::ResultStatus::ErrorBadRelocationBlock; | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | SubsectionBlock subsection_block{}; | ||
| 325 | if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != | ||
| 326 | sizeof(RelocationBlock)) { | ||
| 327 | status = Loader::ResultStatus::ErrorBadSubsectionBlock; | ||
| 328 | return false; | ||
| 329 | } | ||
| 330 | |||
| 331 | std::vector<RelocationBucketRaw> relocation_buckets_raw( | ||
| 332 | (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); | ||
| 333 | if (dec->ReadBytes(relocation_buckets_raw.data(), | ||
| 334 | section.bktr.relocation.size - sizeof(RelocationBlock), | ||
| 335 | section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != | ||
| 336 | section.bktr.relocation.size - sizeof(RelocationBlock)) { | ||
| 337 | status = Loader::ResultStatus::ErrorBadRelocationBuckets; | ||
| 338 | return false; | ||
| 339 | } | 93 | } |
| 340 | 94 | ||
| 341 | std::vector<SubsectionBucketRaw> subsection_buckets_raw( | 95 | if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) { |
| 342 | (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); | 96 | files.push_back(filesystems[i]); |
| 343 | if (dec->ReadBytes(subsection_buckets_raw.data(), | 97 | romfs = files.back(); |
| 344 | section.bktr.subsection.size - sizeof(SubsectionBlock), | ||
| 345 | section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != | ||
| 346 | section.bktr.subsection.size - sizeof(SubsectionBlock)) { | ||
| 347 | status = Loader::ResultStatus::ErrorBadSubsectionBuckets; | ||
| 348 | return false; | ||
| 349 | } | 98 | } |
| 350 | 99 | ||
| 351 | std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); | 100 | if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) { |
| 352 | std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(), | 101 | auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]); |
| 353 | &ConvertRelocationBucketRaw); | 102 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { |
| 354 | std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); | 103 | dirs.push_back(npfs); |
| 355 | std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(), | 104 | if (IsDirectoryExeFS(npfs)) { |
| 356 | &ConvertSubsectionBucketRaw); | 105 | exefs = dirs.back(); |
| 357 | 106 | } else if (IsDirectoryLogoPartition(npfs)) { | |
| 358 | u32 ctr_low; | 107 | logo = dirs.back(); |
| 359 | std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); | 108 | } else { |
| 360 | subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); | 109 | continue; |
| 361 | subsection_buckets.back().entries.push_back({size, {0}, 0}); | ||
| 362 | |||
| 363 | std::optional<Core::Crypto::Key128> key; | ||
| 364 | if (encrypted) { | ||
| 365 | if (has_rights_id) { | ||
| 366 | status = Loader::ResultStatus::Success; | ||
| 367 | key = GetTitlekey(); | ||
| 368 | if (!key) { | ||
| 369 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 370 | return false; | ||
| 371 | } | ||
| 372 | } else { | ||
| 373 | key = GetKeyAreaKey(NCASectionCryptoType::BKTR); | ||
| 374 | if (!key) { | ||
| 375 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 376 | return false; | ||
| 377 | } | 110 | } |
| 378 | } | 111 | } |
| 379 | } | 112 | } |
| 380 | 113 | ||
| 381 | if (bktr_base_romfs == nullptr) { | 114 | if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { |
| 382 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; | 115 | is_update = true; |
| 383 | return false; | ||
| 384 | } | 116 | } |
| 385 | |||
| 386 | auto bktr = std::make_shared<BKTR>( | ||
| 387 | bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), | ||
| 388 | relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted, | ||
| 389 | encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset, | ||
| 390 | section.raw.section_ctr); | ||
| 391 | |||
| 392 | // BKTR applies to entire IVFC, so make an offset version to level 6 | ||
| 393 | files.push_back(std::make_shared<OffsetVfsFile>( | ||
| 394 | bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); | ||
| 395 | } else { | ||
| 396 | files.push_back(std::move(dec)); | ||
| 397 | } | 117 | } |
| 398 | 118 | ||
| 399 | romfs = files.back(); | 119 | if (is_update && base_nca == nullptr) { |
| 400 | return true; | 120 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; |
| 401 | } | ||
| 402 | |||
| 403 | bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { | ||
| 404 | const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + | ||
| 405 | section.pfs0.pfs0_header_offset; | ||
| 406 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 407 | |||
| 408 | auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); | ||
| 409 | if (dec != nullptr) { | ||
| 410 | auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||
| 411 | |||
| 412 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { | ||
| 413 | dirs.push_back(std::move(npfs)); | ||
| 414 | if (IsDirectoryExeFS(dirs.back())) | ||
| 415 | exefs = dirs.back(); | ||
| 416 | else if (IsDirectoryLogoPartition(dirs.back())) | ||
| 417 | logo = dirs.back(); | ||
| 418 | } else { | ||
| 419 | if (has_rights_id) | ||
| 420 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 421 | else | ||
| 422 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 423 | return false; | ||
| 424 | } | ||
| 425 | } else { | 121 | } else { |
| 426 | if (status != Loader::ResultStatus::Success) | 122 | status = Loader::ResultStatus::Success; |
| 427 | return false; | ||
| 428 | if (has_rights_id) | ||
| 429 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 430 | else | ||
| 431 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 432 | return false; | ||
| 433 | } | 123 | } |
| 434 | |||
| 435 | return true; | ||
| 436 | } | ||
| 437 | |||
| 438 | u8 NCA::GetCryptoRevision() const { | ||
| 439 | u8 master_key_id = header.crypto_type; | ||
| 440 | if (header.crypto_type_2 > master_key_id) | ||
| 441 | master_key_id = header.crypto_type_2; | ||
| 442 | if (master_key_id > 0) | ||
| 443 | --master_key_id; | ||
| 444 | return master_key_id; | ||
| 445 | } | ||
| 446 | |||
| 447 | std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | ||
| 448 | const auto master_key_id = GetCryptoRevision(); | ||
| 449 | |||
| 450 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { | ||
| 451 | return std::nullopt; | ||
| 452 | } | ||
| 453 | |||
| 454 | std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | ||
| 455 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 456 | keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||
| 457 | Core::Crypto::Mode::ECB); | ||
| 458 | cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||
| 459 | |||
| 460 | Core::Crypto::Key128 out{}; | ||
| 461 | if (type == NCASectionCryptoType::XTS) { | ||
| 462 | std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||
| 463 | } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { | ||
| 464 | std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||
| 465 | } else { | ||
| 466 | LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | ||
| 467 | type); | ||
| 468 | } | ||
| 469 | |||
| 470 | u128 out_128{}; | ||
| 471 | std::memcpy(out_128.data(), out.data(), sizeof(u128)); | ||
| 472 | LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||
| 473 | master_key_id, header.key_index, out_128[1], out_128[0]); | ||
| 474 | |||
| 475 | return out; | ||
| 476 | } | 124 | } |
| 477 | 125 | ||
| 478 | std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { | 126 | NCA::~NCA() = default; |
| 479 | const auto master_key_id = GetCryptoRevision(); | ||
| 480 | |||
| 481 | u128 rights_id{}; | ||
| 482 | memcpy(rights_id.data(), header.rights_id.data(), 16); | ||
| 483 | if (rights_id == u128{}) { | ||
| 484 | status = Loader::ResultStatus::ErrorInvalidRightsID; | ||
| 485 | return std::nullopt; | ||
| 486 | } | ||
| 487 | |||
| 488 | auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); | ||
| 489 | if (titlekey == Core::Crypto::Key128{}) { | ||
| 490 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 491 | return std::nullopt; | ||
| 492 | } | ||
| 493 | |||
| 494 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { | ||
| 495 | status = Loader::ResultStatus::ErrorMissingTitlekek; | ||
| 496 | return std::nullopt; | ||
| 497 | } | ||
| 498 | |||
| 499 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 500 | keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); | ||
| 501 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); | ||
| 502 | |||
| 503 | return titlekey; | ||
| 504 | } | ||
| 505 | |||
| 506 | VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { | ||
| 507 | if (!encrypted) | ||
| 508 | return in; | ||
| 509 | |||
| 510 | switch (s_header.raw.header.crypto_type) { | ||
| 511 | case NCASectionCryptoType::NONE: | ||
| 512 | LOG_TRACE(Crypto, "called with mode=NONE"); | ||
| 513 | return in; | ||
| 514 | case NCASectionCryptoType::CTR: | ||
| 515 | // During normal BKTR decryption, this entire function is skipped. This is for the metadata, | ||
| 516 | // which uses the same CTR as usual. | ||
| 517 | case NCASectionCryptoType::BKTR: | ||
| 518 | LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||
| 519 | { | ||
| 520 | std::optional<Core::Crypto::Key128> key; | ||
| 521 | if (has_rights_id) { | ||
| 522 | status = Loader::ResultStatus::Success; | ||
| 523 | key = GetTitlekey(); | ||
| 524 | if (!key) { | ||
| 525 | if (status == Loader::ResultStatus::Success) | ||
| 526 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 527 | return nullptr; | ||
| 528 | } | ||
| 529 | } else { | ||
| 530 | key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||
| 531 | if (!key) { | ||
| 532 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 533 | return nullptr; | ||
| 534 | } | ||
| 535 | } | ||
| 536 | |||
| 537 | auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key, | ||
| 538 | starting_offset); | ||
| 539 | Core::Crypto::CTREncryptionLayer::IVData iv{}; | ||
| 540 | for (std::size_t i = 0; i < 8; ++i) { | ||
| 541 | iv[i] = s_header.raw.section_ctr[8 - i - 1]; | ||
| 542 | } | ||
| 543 | out->SetIV(iv); | ||
| 544 | return std::static_pointer_cast<VfsFile>(out); | ||
| 545 | } | ||
| 546 | case NCASectionCryptoType::XTS: | ||
| 547 | // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs | ||
| 548 | default: | ||
| 549 | LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||
| 550 | s_header.raw.header.crypto_type); | ||
| 551 | return nullptr; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | 127 | ||
| 555 | Loader::ResultStatus NCA::GetStatus() const { | 128 | Loader::ResultStatus NCA::GetStatus() const { |
| 556 | return status; | 129 | return status; |
| @@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const { | |||
| 579 | } | 152 | } |
| 580 | 153 | ||
| 581 | NCAContentType NCA::GetType() const { | 154 | NCAContentType NCA::GetType() const { |
| 582 | return header.content_type; | 155 | return static_cast<NCAContentType>(reader->GetContentType()); |
| 583 | } | 156 | } |
| 584 | 157 | ||
| 585 | u64 NCA::GetTitleId() const { | 158 | u64 NCA::GetTitleId() const { |
| 586 | if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) | 159 | if (is_update) { |
| 587 | return header.title_id | 0x800; | 160 | return reader->GetProgramId() | 0x800; |
| 588 | return header.title_id; | 161 | } |
| 162 | return reader->GetProgramId(); | ||
| 589 | } | 163 | } |
| 590 | 164 | ||
| 591 | std::array<u8, 16> NCA::GetRightsId() const { | 165 | RightsId NCA::GetRightsId() const { |
| 592 | return header.rights_id; | 166 | RightsId result; |
| 167 | reader->GetRightsId(result.data(), result.size()); | ||
| 168 | return result; | ||
| 593 | } | 169 | } |
| 594 | 170 | ||
| 595 | u32 NCA::GetSDKVersion() const { | 171 | u32 NCA::GetSDKVersion() const { |
| 596 | return header.sdk_version; | 172 | return reader->GetSdkAddonVersion(); |
| 597 | } | 173 | } |
| 598 | 174 | ||
| 599 | bool NCA::IsUpdate() const { | 175 | bool NCA::IsUpdate() const { |
| @@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const { | |||
| 612 | return file; | 188 | return file; |
| 613 | } | 189 | } |
| 614 | 190 | ||
| 615 | u64 NCA::GetBaseIVFCOffset() const { | ||
| 616 | return ivfc_offset; | ||
| 617 | } | ||
| 618 | |||
| 619 | VirtualDir NCA::GetLogoPartition() const { | 191 | VirtualDir NCA::GetLogoPartition() const { |
| 620 | return logo; | 192 | return logo; |
| 621 | } | 193 | } |
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 20f524f80..af521d453 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h | |||
| @@ -21,7 +21,7 @@ enum class ResultStatus : u16; | |||
| 21 | 21 | ||
| 22 | namespace FileSys { | 22 | namespace FileSys { |
| 23 | 23 | ||
| 24 | union NCASectionHeader; | 24 | class NcaReader; |
| 25 | 25 | ||
| 26 | /// Describes the type of content within an NCA archive. | 26 | /// Describes the type of content within an NCA archive. |
| 27 | enum class NCAContentType : u8 { | 27 | enum class NCAContentType : u8 { |
| @@ -45,41 +45,7 @@ enum class NCAContentType : u8 { | |||
| 45 | PublicData = 5, | 45 | PublicData = 5, |
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | enum class NCASectionCryptoType : u8 { | 48 | using RightsId = std::array<u8, 0x10>; |
| 49 | NONE = 1, | ||
| 50 | XTS = 2, | ||
| 51 | CTR = 3, | ||
| 52 | BKTR = 4, | ||
| 53 | }; | ||
| 54 | |||
| 55 | struct NCASectionTableEntry { | ||
| 56 | u32_le media_offset; | ||
| 57 | u32_le media_end_offset; | ||
| 58 | INSERT_PADDING_BYTES(0x8); | ||
| 59 | }; | ||
| 60 | static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); | ||
| 61 | |||
| 62 | struct NCAHeader { | ||
| 63 | std::array<u8, 0x100> rsa_signature_1; | ||
| 64 | std::array<u8, 0x100> rsa_signature_2; | ||
| 65 | u32_le magic; | ||
| 66 | u8 is_system; | ||
| 67 | NCAContentType content_type; | ||
| 68 | u8 crypto_type; | ||
| 69 | u8 key_index; | ||
| 70 | u64_le size; | ||
| 71 | u64_le title_id; | ||
| 72 | INSERT_PADDING_BYTES(0x4); | ||
| 73 | u32_le sdk_version; | ||
| 74 | u8 crypto_type_2; | ||
| 75 | INSERT_PADDING_BYTES(15); | ||
| 76 | std::array<u8, 0x10> rights_id; | ||
| 77 | std::array<NCASectionTableEntry, 0x4> section_tables; | ||
| 78 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; | ||
| 79 | std::array<u8, 0x40> key_area; | ||
| 80 | INSERT_PADDING_BYTES(0xC0); | ||
| 81 | }; | ||
| 82 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | ||
| 83 | 49 | ||
| 84 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { | 50 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { |
| 85 | // According to switchbrew, an exefs must only contain these two files: | 51 | // According to switchbrew, an exefs must only contain these two files: |
| @@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { | |||
| 97 | // After construction, use GetStatus to determine if the file is valid and ready to be used. | 63 | // After construction, use GetStatus to determine if the file is valid and ready to be used. |
| 98 | class NCA : public ReadOnlyVfsDirectory { | 64 | class NCA : public ReadOnlyVfsDirectory { |
| 99 | public: | 65 | public: |
| 100 | explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, | 66 | explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); |
| 101 | u64 bktr_base_ivfc_offset = 0); | ||
| 102 | ~NCA() override; | 67 | ~NCA() override; |
| 103 | 68 | ||
| 104 | Loader::ResultStatus GetStatus() const; | 69 | Loader::ResultStatus GetStatus() const; |
| @@ -110,7 +75,7 @@ public: | |||
| 110 | 75 | ||
| 111 | NCAContentType GetType() const; | 76 | NCAContentType GetType() const; |
| 112 | u64 GetTitleId() const; | 77 | u64 GetTitleId() const; |
| 113 | std::array<u8, 0x10> GetRightsId() const; | 78 | RightsId GetRightsId() const; |
| 114 | u32 GetSDKVersion() const; | 79 | u32 GetSDKVersion() const; |
| 115 | bool IsUpdate() const; | 80 | bool IsUpdate() const; |
| 116 | 81 | ||
| @@ -119,26 +84,9 @@ public: | |||
| 119 | 84 | ||
| 120 | VirtualFile GetBaseFile() const; | 85 | VirtualFile GetBaseFile() const; |
| 121 | 86 | ||
| 122 | // Returns the base ivfc offset used in BKTR patching. | ||
| 123 | u64 GetBaseIVFCOffset() const; | ||
| 124 | |||
| 125 | VirtualDir GetLogoPartition() const; | 87 | VirtualDir GetLogoPartition() const; |
| 126 | 88 | ||
| 127 | private: | 89 | private: |
| 128 | bool CheckSupportedNCA(const NCAHeader& header); | ||
| 129 | bool HandlePotentialHeaderDecryption(); | ||
| 130 | |||
| 131 | std::vector<NCASectionHeader> ReadSectionHeaders() const; | ||
| 132 | bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset); | ||
| 133 | bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 134 | u64 bktr_base_ivfc_offset); | ||
| 135 | bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); | ||
| 136 | |||
| 137 | u8 GetCryptoRevision() const; | ||
| 138 | std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||
| 139 | std::optional<Core::Crypto::Key128> GetTitlekey(); | ||
| 140 | VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); | ||
| 141 | |||
| 142 | std::vector<VirtualDir> dirs; | 90 | std::vector<VirtualDir> dirs; |
| 143 | std::vector<VirtualFile> files; | 91 | std::vector<VirtualFile> files; |
| 144 | 92 | ||
| @@ -146,11 +94,6 @@ private: | |||
| 146 | VirtualDir exefs = nullptr; | 94 | VirtualDir exefs = nullptr; |
| 147 | VirtualDir logo = nullptr; | 95 | VirtualDir logo = nullptr; |
| 148 | VirtualFile file; | 96 | VirtualFile file; |
| 149 | VirtualFile bktr_base_romfs; | ||
| 150 | u64 ivfc_offset = 0; | ||
| 151 | |||
| 152 | NCAHeader header{}; | ||
| 153 | bool has_rights_id{}; | ||
| 154 | 97 | ||
| 155 | Loader::ResultStatus status{}; | 98 | Loader::ResultStatus status{}; |
| 156 | 99 | ||
| @@ -158,6 +101,7 @@ private: | |||
| 158 | bool is_update = false; | 101 | bool is_update = false; |
| 159 | 102 | ||
| 160 | Core::Crypto::KeyManager& keys; | 103 | Core::Crypto::KeyManager& keys; |
| 104 | std::shared_ptr<NcaReader> reader; | ||
| 161 | }; | 105 | }; |
| 162 | 106 | ||
| 163 | } // namespace FileSys | 107 | } // namespace FileSys |
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 7cee0c7df..2f5045a67 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h | |||
| @@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; | |||
| 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; | 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; |
| 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; | 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; |
| 19 | 19 | ||
| 20 | constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50}; | ||
| 21 | constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001}; | ||
| 22 | constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002}; | ||
| 23 | constexpr Result ResultOutOfRange{ErrorModule::FS, 3005}; | ||
| 24 | constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294}; | ||
| 25 | constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341}; | ||
| 26 | constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363}; | ||
| 27 | constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399}; | ||
| 28 | constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412}; | ||
| 29 | constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422}; | ||
| 30 | constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423}; | ||
| 31 | constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012}; | ||
| 32 | constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021}; | ||
| 33 | constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022}; | ||
| 34 | constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023}; | ||
| 35 | constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024}; | ||
| 36 | constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032}; | ||
| 37 | constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033}; | ||
| 38 | constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034}; | ||
| 39 | constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035}; | ||
| 40 | constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036}; | ||
| 41 | constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037}; | ||
| 42 | constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038}; | ||
| 43 | constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039}; | ||
| 44 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084}; | ||
| 45 | constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085}; | ||
| 46 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086}; | ||
| 47 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087}; | ||
| 48 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088}; | ||
| 49 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089}; | ||
| 50 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090}; | ||
| 51 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091}; | ||
| 52 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091}; | ||
| 53 | constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509}; | ||
| 54 | constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510}; | ||
| 55 | constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511}; | ||
| 56 | constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517}; | ||
| 57 | constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520}; | ||
| 58 | constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521}; | ||
| 59 | constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522}; | ||
| 60 | constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523}; | ||
| 61 | constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524}; | ||
| 62 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525}; | ||
| 63 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526}; | ||
| 64 | constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528}; | ||
| 65 | constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529}; | ||
| 66 | constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530}; | ||
| 67 | constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532}; | ||
| 68 | constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533}; | ||
| 69 | constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534}; | ||
| 70 | constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535}; | ||
| 71 | constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541}; | ||
| 72 | constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542}; | ||
| 73 | constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543}; | ||
| 74 | constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547}; | ||
| 75 | constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548}; | ||
| 76 | constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549}; | ||
| 77 | constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324}; | ||
| 78 | constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; | ||
| 79 | constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; | ||
| 80 | constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; | ||
| 81 | constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; | ||
| 82 | constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; | ||
| 83 | constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; | ||
| 84 | constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; | ||
| 85 | constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; | ||
| 86 | constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; | ||
| 87 | constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; | ||
| 88 | constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705}; | ||
| 89 | |||
| 20 | } // namespace FileSys | 90 | } // namespace FileSys |
diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h new file mode 100644 index 000000000..416dd57b8 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_i_storage.h | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/overflow.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class IStorage : public VfsFile { | ||
| 13 | public: | ||
| 14 | virtual std::string GetName() const override { | ||
| 15 | return {}; | ||
| 16 | } | ||
| 17 | |||
| 18 | virtual VirtualDir GetContainingDirectory() const override { | ||
| 19 | return {}; | ||
| 20 | } | ||
| 21 | |||
| 22 | virtual bool IsWritable() const override { | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | virtual bool IsReadable() const override { | ||
| 27 | return true; | ||
| 28 | } | ||
| 29 | |||
| 30 | virtual bool Resize(size_t size) override { | ||
| 31 | return false; | ||
| 32 | } | ||
| 33 | |||
| 34 | virtual bool Rename(std::string_view name) override { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | |||
| 38 | static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { | ||
| 39 | R_UNLESS(offset >= 0, ResultInvalidOffset); | ||
| 40 | R_UNLESS(size >= 0, ResultInvalidSize); | ||
| 41 | R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange); | ||
| 42 | R_UNLESS(offset + size <= total_size, ResultOutOfRange); | ||
| 43 | R_SUCCEED(); | ||
| 44 | } | ||
| 45 | }; | ||
| 46 | |||
| 47 | class IReadOnlyStorage : public IStorage { | ||
| 48 | public: | ||
| 49 | virtual bool IsWritable() const override { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 54 | return 0; | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h new file mode 100644 index 000000000..43aeaf447 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_types.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | struct Int64 { | ||
| 11 | u32 low; | ||
| 12 | u32 high; | ||
| 13 | |||
| 14 | constexpr void Set(s64 v) { | ||
| 15 | this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0); | ||
| 16 | this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32); | ||
| 17 | } | ||
| 18 | |||
| 19 | constexpr s64 Get() const { | ||
| 20 | return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low)); | ||
| 21 | } | ||
| 22 | |||
| 23 | constexpr Int64& operator=(s64 v) { | ||
| 24 | this->Set(v); | ||
| 25 | return *this; | ||
| 26 | } | ||
| 27 | |||
| 28 | constexpr operator s64() const { | ||
| 29 | return this->Get(); | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct HashSalt { | ||
| 34 | static constexpr size_t Size = 32; | ||
| 35 | |||
| 36 | std::array<u8, Size> value; | ||
| 37 | }; | ||
| 38 | static_assert(std::is_trivial_v<HashSalt>); | ||
| 39 | static_assert(sizeof(HashSalt) == HashSalt::Size); | ||
| 40 | |||
| 41 | constexpr inline size_t IntegrityMinLayerCount = 2; | ||
| 42 | constexpr inline size_t IntegrityMaxLayerCount = 7; | ||
| 43 | constexpr inline size_t IntegrityLayerCountSave = 5; | ||
| 44 | constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; | ||
| 45 | |||
| 46 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 000000000..f25c95472 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | |||
| @@ -0,0 +1,251 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 7 | #include "core/file_sys/vfs_offset.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { | ||
| 14 | public: | ||
| 15 | virtual void Decrypt( | ||
| 16 | u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 17 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final; | ||
| 18 | }; | ||
| 19 | |||
| 20 | } // namespace | ||
| 21 | |||
| 22 | Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) { | ||
| 23 | std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>(); | ||
| 24 | R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA); | ||
| 25 | *out = std::move(decryptor); | ||
| 26 | R_SUCCEED(); | ||
| 27 | } | ||
| 28 | |||
| 29 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 30 | VirtualFile data_storage, | ||
| 31 | VirtualFile table_storage) { | ||
| 32 | // Read and verify the bucket tree header. | ||
| 33 | BucketTree::Header header; | ||
| 34 | table_storage->ReadObject(std::addressof(header), 0); | ||
| 35 | R_TRY(header.Verify()); | ||
| 36 | |||
| 37 | // Determine extents. | ||
| 38 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 39 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 40 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 41 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 42 | |||
| 43 | // Create a software decryptor. | ||
| 44 | std::unique_ptr<IDecryptor> sw_decryptor; | ||
| 45 | R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 46 | |||
| 47 | // Initialize. | ||
| 48 | R_RETURN(this->Initialize( | ||
| 49 | key, key_size, secure_value, 0, data_storage, | ||
| 50 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 51 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 52 | header.entry_count, std::move(sw_decryptor))); | ||
| 53 | } | ||
| 54 | |||
| 55 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 56 | s64 counter_offset, VirtualFile data_storage, | ||
| 57 | VirtualFile node_storage, VirtualFile entry_storage, | ||
| 58 | s32 entry_count, | ||
| 59 | std::unique_ptr<IDecryptor>&& decryptor) { | ||
| 60 | // Validate preconditions. | ||
| 61 | ASSERT(key != nullptr); | ||
| 62 | ASSERT(key_size == KeySize); | ||
| 63 | ASSERT(counter_offset >= 0); | ||
| 64 | ASSERT(decryptor != nullptr); | ||
| 65 | |||
| 66 | // Initialize the bucket tree table. | ||
| 67 | if (entry_count > 0) { | ||
| 68 | R_TRY( | ||
| 69 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 70 | } else { | ||
| 71 | m_table.Initialize(NodeSize, 0); | ||
| 72 | } | ||
| 73 | |||
| 74 | // Set members. | ||
| 75 | m_data_storage = data_storage; | ||
| 76 | std::memcpy(m_key.data(), key, key_size); | ||
| 77 | m_secure_value = secure_value; | ||
| 78 | m_counter_offset = counter_offset; | ||
| 79 | m_decryptor = std::move(decryptor); | ||
| 80 | |||
| 81 | R_SUCCEED(); | ||
| 82 | } | ||
| 83 | |||
| 84 | void AesCtrCounterExtendedStorage::Finalize() { | ||
| 85 | if (this->IsInitialized()) { | ||
| 86 | m_table.Finalize(); | ||
| 87 | m_data_storage = VirtualFile(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, | ||
| 92 | s32 entry_count, s64 offset, s64 size) { | ||
| 93 | // Validate pre-conditions. | ||
| 94 | ASSERT(offset >= 0); | ||
| 95 | ASSERT(size >= 0); | ||
| 96 | ASSERT(this->IsInitialized()); | ||
| 97 | |||
| 98 | // Clear the out count. | ||
| 99 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 100 | *out_entry_count = 0; | ||
| 101 | |||
| 102 | // Succeed if there's no range. | ||
| 103 | R_SUCCEED_IF(size == 0); | ||
| 104 | |||
| 105 | // If we have an output array, we need it to be non-null. | ||
| 106 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 107 | |||
| 108 | // Check that our range is valid. | ||
| 109 | BucketTree::Offsets table_offsets; | ||
| 110 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 111 | |||
| 112 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 113 | |||
| 114 | // Find the offset in our tree. | ||
| 115 | BucketTree::Visitor visitor; | ||
| 116 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 117 | { | ||
| 118 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 119 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 120 | ResultInvalidAesCtrCounterExtendedEntryOffset); | ||
| 121 | } | ||
| 122 | |||
| 123 | // Prepare to loop over entries. | ||
| 124 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 125 | s32 count = 0; | ||
| 126 | |||
| 127 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 128 | while (cur_entry.GetOffset() < end_offset) { | ||
| 129 | // Try to write the entry to the out list. | ||
| 130 | if (entry_count != 0) { | ||
| 131 | if (count >= entry_count) { | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 135 | } | ||
| 136 | |||
| 137 | count++; | ||
| 138 | |||
| 139 | // Advance. | ||
| 140 | if (visitor.CanMoveNext()) { | ||
| 141 | R_TRY(visitor.MoveNext()); | ||
| 142 | cur_entry = *visitor.Get<Entry>(); | ||
| 143 | } else { | ||
| 144 | break; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | // Write the output count. | ||
| 149 | *out_entry_count = count; | ||
| 150 | R_SUCCEED(); | ||
| 151 | } | ||
| 152 | |||
| 153 | size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 154 | // Validate preconditions. | ||
| 155 | ASSERT(this->IsInitialized()); | ||
| 156 | |||
| 157 | // Allow zero size. | ||
| 158 | if (size == 0) { | ||
| 159 | return size; | ||
| 160 | } | ||
| 161 | |||
| 162 | // Validate arguments. | ||
| 163 | ASSERT(buffer != nullptr); | ||
| 164 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 165 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 166 | |||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets)))); | ||
| 169 | |||
| 170 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 171 | |||
| 172 | // Read the data. | ||
| 173 | m_data_storage->Read(buffer, size, offset); | ||
| 174 | |||
| 175 | // Find the offset in our tree. | ||
| 176 | BucketTree::Visitor visitor; | ||
| 177 | ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset))); | ||
| 178 | { | ||
| 179 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 180 | ASSERT(Common::IsAligned(entry_offset, BlockSize)); | ||
| 181 | ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset)); | ||
| 182 | } | ||
| 183 | |||
| 184 | // Prepare to read in chunks. | ||
| 185 | u8* cur_data = static_cast<u8*>(buffer); | ||
| 186 | auto cur_offset = offset; | ||
| 187 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 188 | |||
| 189 | while (cur_offset < end_offset) { | ||
| 190 | // Get the current entry. | ||
| 191 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 192 | |||
| 193 | // Get and validate the entry's offset. | ||
| 194 | const auto cur_entry_offset = cur_entry.GetOffset(); | ||
| 195 | ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset); | ||
| 196 | |||
| 197 | // Get and validate the next entry offset. | ||
| 198 | s64 next_entry_offset; | ||
| 199 | if (visitor.CanMoveNext()) { | ||
| 200 | ASSERT(R_SUCCEEDED(visitor.MoveNext())); | ||
| 201 | next_entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 202 | ASSERT(table_offsets.IsInclude(next_entry_offset)); | ||
| 203 | } else { | ||
| 204 | next_entry_offset = table_offsets.end_offset; | ||
| 205 | } | ||
| 206 | ASSERT(Common::IsAligned(next_entry_offset, BlockSize)); | ||
| 207 | ASSERT(cur_offset < static_cast<size_t>(next_entry_offset)); | ||
| 208 | |||
| 209 | // Get the offset of the entry in the data we read. | ||
| 210 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 211 | const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; | ||
| 212 | ASSERT(data_size > 0); | ||
| 213 | |||
| 214 | // Determine how much is left. | ||
| 215 | const auto remaining_size = end_offset - cur_offset; | ||
| 216 | const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size)); | ||
| 217 | ASSERT(cur_size <= size); | ||
| 218 | |||
| 219 | // If necessary, perform decryption. | ||
| 220 | if (cur_entry.encryption_value == Entry::Encryption::Encrypted) { | ||
| 221 | // Make the CTR for the data we're decrypting. | ||
| 222 | const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset; | ||
| 223 | NcaAesCtrUpperIv upper_iv = { | ||
| 224 | .part = {.generation = static_cast<u32>(cur_entry.generation), | ||
| 225 | .secure_value = m_secure_value}}; | ||
| 226 | |||
| 227 | std::array<u8, IvSize> iv; | ||
| 228 | AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset); | ||
| 229 | |||
| 230 | // Decrypt. | ||
| 231 | m_decryptor->Decrypt(cur_data, cur_size, m_key, iv); | ||
| 232 | } | ||
| 233 | |||
| 234 | // Advance. | ||
| 235 | cur_data += cur_size; | ||
| 236 | cur_offset += cur_size; | ||
| 237 | } | ||
| 238 | |||
| 239 | return size; | ||
| 240 | } | ||
| 241 | |||
| 242 | void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, | ||
| 243 | const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 244 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) { | ||
| 245 | Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher( | ||
| 246 | key, Core::Crypto::Mode::CTR); | ||
| 247 | cipher.SetIV(iv); | ||
| 248 | cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt); | ||
| 249 | } | ||
| 250 | |||
| 251 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h new file mode 100644 index 000000000..d0e9ceed0 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | using namespace Common::Literals; | ||
| 15 | |||
| 16 | class AesCtrCounterExtendedStorage : public IReadOnlyStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | class IDecryptor { | ||
| 27 | public: | ||
| 28 | virtual ~IDecryptor() {} | ||
| 29 | virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key, | ||
| 30 | const std::array<u8, IvSize>& iv) = 0; | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct Entry { | ||
| 34 | enum class Encryption : u8 { | ||
| 35 | Encrypted = 0, | ||
| 36 | NotEncrypted = 1, | ||
| 37 | }; | ||
| 38 | |||
| 39 | std::array<u8, sizeof(s64)> offset; | ||
| 40 | Encryption encryption_value; | ||
| 41 | std::array<u8, 3> reserved; | ||
| 42 | s32 generation; | ||
| 43 | |||
| 44 | void SetOffset(s64 value) { | ||
| 45 | std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64)); | ||
| 46 | } | ||
| 47 | |||
| 48 | s64 GetOffset() const { | ||
| 49 | s64 value; | ||
| 50 | std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64)); | ||
| 51 | return value; | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | static_assert(sizeof(Entry) == 0x10); | ||
| 55 | static_assert(alignof(Entry) == 4); | ||
| 56 | static_assert(std::is_trivial_v<Entry>); | ||
| 57 | |||
| 58 | public: | ||
| 59 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 60 | return BucketTree::QueryHeaderStorageSize(); | ||
| 61 | } | ||
| 62 | |||
| 63 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 64 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 65 | } | ||
| 66 | |||
| 67 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 68 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 69 | } | ||
| 70 | |||
| 71 | static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out); | ||
| 72 | |||
| 73 | public: | ||
| 74 | AesCtrCounterExtendedStorage() | ||
| 75 | : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {} | ||
| 76 | virtual ~AesCtrCounterExtendedStorage() { | ||
| 77 | this->Finalize(); | ||
| 78 | } | ||
| 79 | |||
| 80 | Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset, | ||
| 81 | VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 82 | s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor); | ||
| 83 | void Finalize(); | ||
| 84 | |||
| 85 | bool IsInitialized() const { | ||
| 86 | return m_table.IsInitialized(); | ||
| 87 | } | ||
| 88 | |||
| 89 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 90 | |||
| 91 | virtual size_t GetSize() const override { | ||
| 92 | BucketTree::Offsets offsets; | ||
| 93 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets)))); | ||
| 94 | |||
| 95 | return offsets.end_offset; | ||
| 96 | } | ||
| 97 | |||
| 98 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 99 | s64 size); | ||
| 100 | |||
| 101 | private: | ||
| 102 | Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage, | ||
| 103 | VirtualFile table_storage); | ||
| 104 | |||
| 105 | private: | ||
| 106 | mutable BucketTree m_table; | ||
| 107 | VirtualFile m_data_storage; | ||
| 108 | std::array<u8, KeySize> m_key; | ||
| 109 | u32 m_secure_value; | ||
| 110 | s64 m_counter_offset; | ||
| 111 | std::unique_ptr<IDecryptor> m_decryptor; | ||
| 112 | }; | ||
| 113 | |||
| 114 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp new file mode 100644 index 000000000..b65aca18d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) { | ||
| 13 | ASSERT(dst != nullptr); | ||
| 14 | ASSERT(dst_size == IvSize); | ||
| 15 | ASSERT(offset >= 0); | ||
| 16 | |||
| 17 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 18 | |||
| 19 | *reinterpret_cast<u64_be*>(out_addr + 0) = upper; | ||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize); | ||
| 21 | } | ||
| 22 | |||
| 23 | AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 24 | size_t iv_size) | ||
| 25 | : m_base_storage(std::move(base)) { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key != nullptr); | ||
| 28 | ASSERT(iv != nullptr); | ||
| 29 | ASSERT(key_size == KeySize); | ||
| 30 | ASSERT(iv_size == IvSize); | ||
| 31 | |||
| 32 | std::memcpy(m_key.data(), key, KeySize); | ||
| 33 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 34 | |||
| 35 | m_cipher.emplace(m_key, Core::Crypto::Mode::CTR); | ||
| 36 | } | ||
| 37 | |||
| 38 | size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 39 | // Allow zero-size reads. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Ensure buffer is valid. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | // We can only read at block aligned offsets. | ||
| 48 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 49 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 50 | |||
| 51 | // Read the data. | ||
| 52 | m_base_storage->Read(buffer, size, offset); | ||
| 53 | |||
| 54 | // Setup the counter. | ||
| 55 | std::array<u8, IvSize> ctr; | ||
| 56 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 57 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 58 | |||
| 59 | // Decrypt. | ||
| 60 | m_cipher->SetIV(ctr); | ||
| 61 | m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt); | ||
| 62 | |||
| 63 | return size; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) { | ||
| 67 | // Allow zero-size writes. | ||
| 68 | if (size == 0) { | ||
| 69 | return size; | ||
| 70 | } | ||
| 71 | |||
| 72 | // Ensure buffer is valid. | ||
| 73 | ASSERT(buffer != nullptr); | ||
| 74 | |||
| 75 | // We can only write at block aligned offsets. | ||
| 76 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 77 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 78 | |||
| 79 | // Get a pooled buffer. | ||
| 80 | PooledBuffer pooled_buffer; | ||
| 81 | const bool use_work_buffer = true; | ||
| 82 | if (use_work_buffer) { | ||
| 83 | pooled_buffer.Allocate(size, BlockSize); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Setup the counter. | ||
| 87 | std::array<u8, IvSize> ctr; | ||
| 88 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 89 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 90 | |||
| 91 | // Loop until all data is written. | ||
| 92 | size_t remaining = size; | ||
| 93 | s64 cur_offset = 0; | ||
| 94 | while (remaining > 0) { | ||
| 95 | // Determine data we're writing and where. | ||
| 96 | const size_t write_size = | ||
| 97 | use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; | ||
| 98 | |||
| 99 | void* write_buf; | ||
| 100 | if (use_work_buffer) { | ||
| 101 | write_buf = pooled_buffer.GetBuffer(); | ||
| 102 | } else { | ||
| 103 | write_buf = const_cast<u8*>(buffer); | ||
| 104 | } | ||
| 105 | |||
| 106 | // Encrypt the data. | ||
| 107 | m_cipher->SetIV(ctr); | ||
| 108 | m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf), | ||
| 109 | Core::Crypto::Op::Encrypt); | ||
| 110 | |||
| 111 | // Write the encrypted data. | ||
| 112 | m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset); | ||
| 113 | |||
| 114 | // Advance. | ||
| 115 | cur_offset += write_size; | ||
| 116 | remaining -= write_size; | ||
| 117 | if (remaining > 0) { | ||
| 118 | AddCounter(ctr.data(), IvSize, write_size / BlockSize); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | return size; | ||
| 123 | } | ||
| 124 | |||
| 125 | size_t AesCtrStorage::GetSize() const { | ||
| 126 | return m_base_storage->GetSize(); | ||
| 127 | } | ||
| 128 | |||
| 129 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h new file mode 100644 index 000000000..339e49697 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 12 | #include "core/file_sys/vfs.h" | ||
| 13 | |||
| 14 | namespace FileSys { | ||
| 15 | |||
| 16 | class AesCtrStorage : public IStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | |||
| 25 | public: | ||
| 26 | static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset); | ||
| 27 | |||
| 28 | public: | ||
| 29 | AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 30 | size_t iv_size); | ||
| 31 | |||
| 32 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 33 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override; | ||
| 34 | virtual size_t GetSize() const override; | ||
| 35 | |||
| 36 | private: | ||
| 37 | VirtualFile m_base_storage; | ||
| 38 | std::array<u8, KeySize> m_key; | ||
| 39 | std::array<u8, IvSize> m_iv; | ||
| 40 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher; | ||
| 41 | }; | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp new file mode 100644 index 000000000..022424229 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) { | ||
| 14 | ASSERT(dst != nullptr); | ||
| 15 | ASSERT(dst_size == IvSize); | ||
| 16 | ASSERT(offset >= 0); | ||
| 17 | |||
| 18 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 19 | |||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size; | ||
| 21 | } | ||
| 22 | |||
| 23 | AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 24 | const void* iv, size_t iv_size, size_t block_size) | ||
| 25 | : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key1 != nullptr); | ||
| 28 | ASSERT(key2 != nullptr); | ||
| 29 | ASSERT(iv != nullptr); | ||
| 30 | ASSERT(key_size == KeySize); | ||
| 31 | ASSERT(iv_size == IvSize); | ||
| 32 | ASSERT(Common::IsAligned(m_block_size, AesBlockSize)); | ||
| 33 | |||
| 34 | std::memcpy(m_key.data() + 0, key1, KeySize); | ||
| 35 | std::memcpy(m_key.data() + 0x10, key2, KeySize); | ||
| 36 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 37 | |||
| 38 | m_cipher.emplace(m_key, Core::Crypto::Mode::XTS); | ||
| 39 | } | ||
| 40 | |||
| 41 | size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 42 | // Allow zero-size reads. | ||
| 43 | if (size == 0) { | ||
| 44 | return size; | ||
| 45 | } | ||
| 46 | |||
| 47 | // Ensure buffer is valid. | ||
| 48 | ASSERT(buffer != nullptr); | ||
| 49 | |||
| 50 | // We can only read at block aligned offsets. | ||
| 51 | ASSERT(Common::IsAligned(offset, AesBlockSize)); | ||
| 52 | ASSERT(Common::IsAligned(size, AesBlockSize)); | ||
| 53 | |||
| 54 | // Read the data. | ||
| 55 | m_base_storage->Read(buffer, size, offset); | ||
| 56 | |||
| 57 | // Setup the counter. | ||
| 58 | std::array<u8, IvSize> ctr; | ||
| 59 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 60 | AddCounter(ctr.data(), IvSize, offset / m_block_size); | ||
| 61 | |||
| 62 | // Handle any unaligned data before the start. | ||
| 63 | size_t processed_size = 0; | ||
| 64 | if ((offset % m_block_size) != 0) { | ||
| 65 | // Determine the size of the pre-data read. | ||
| 66 | const size_t skip_size = | ||
| 67 | static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size)); | ||
| 68 | const size_t data_size = std::min(size, m_block_size - skip_size); | ||
| 69 | |||
| 70 | // Decrypt into a pooled buffer. | ||
| 71 | { | ||
| 72 | PooledBuffer tmp_buf(m_block_size, m_block_size); | ||
| 73 | ASSERT(tmp_buf.GetSize() >= m_block_size); | ||
| 74 | |||
| 75 | std::memset(tmp_buf.GetBuffer(), 0, skip_size); | ||
| 76 | std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); | ||
| 77 | |||
| 78 | m_cipher->SetIV(ctr); | ||
| 79 | m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(), | ||
| 80 | Core::Crypto::Op::Decrypt); | ||
| 81 | |||
| 82 | std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); | ||
| 83 | } | ||
| 84 | |||
| 85 | AddCounter(ctr.data(), IvSize, 1); | ||
| 86 | processed_size += data_size; | ||
| 87 | ASSERT(processed_size == std::min(size, m_block_size - skip_size)); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Decrypt aligned chunks. | ||
| 91 | char* cur = reinterpret_cast<char*>(buffer) + processed_size; | ||
| 92 | size_t remaining = size - processed_size; | ||
| 93 | while (remaining > 0) { | ||
| 94 | const size_t cur_size = std::min(m_block_size, remaining); | ||
| 95 | |||
| 96 | m_cipher->SetIV(ctr); | ||
| 97 | m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt); | ||
| 98 | |||
| 99 | remaining -= cur_size; | ||
| 100 | cur += cur_size; | ||
| 101 | |||
| 102 | AddCounter(ctr.data(), IvSize, 1); | ||
| 103 | } | ||
| 104 | |||
| 105 | return size; | ||
| 106 | } | ||
| 107 | |||
| 108 | size_t AesXtsStorage::GetSize() const { | ||
| 109 | return m_base_storage->GetSize(); | ||
| 110 | } | ||
| 111 | |||
| 112 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h new file mode 100644 index 000000000..f342efb57 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class AesXtsStorage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(AesXtsStorage); | ||
| 16 | YUZU_NON_MOVEABLE(AesXtsStorage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr size_t AesBlockSize = 0x10; | ||
| 20 | static constexpr size_t KeySize = 0x20; | ||
| 21 | static constexpr size_t IvSize = 0x10; | ||
| 22 | |||
| 23 | public: | ||
| 24 | static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size); | ||
| 25 | |||
| 26 | public: | ||
| 27 | AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 28 | const void* iv, size_t iv_size, size_t block_size); | ||
| 29 | |||
| 30 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 31 | virtual size_t GetSize() const override; | ||
| 32 | |||
| 33 | private: | ||
| 34 | VirtualFile m_base_storage; | ||
| 35 | std::array<u8, KeySize> m_key; | ||
| 36 | std::array<u8, IvSize> m_iv; | ||
| 37 | const size_t m_block_size; | ||
| 38 | std::mutex m_mutex; | ||
| 39 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h new file mode 100644 index 000000000..f96691d03 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | template <size_t DataAlign_, size_t BufferAlign_> | ||
| 15 | class AlignmentMatchingStorage : public IStorage { | ||
| 16 | YUZU_NON_COPYABLE(AlignmentMatchingStorage); | ||
| 17 | YUZU_NON_MOVEABLE(AlignmentMatchingStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr size_t DataAlign = DataAlign_; | ||
| 21 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 22 | |||
| 23 | static constexpr size_t DataAlignMax = 0x200; | ||
| 24 | static_assert(DataAlign <= DataAlignMax); | ||
| 25 | static_assert(Common::IsPowerOfTwo(DataAlign)); | ||
| 26 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 27 | |||
| 28 | private: | ||
| 29 | VirtualFile m_base_storage; | ||
| 30 | s64 m_base_storage_size; | ||
| 31 | |||
| 32 | public: | ||
| 33 | explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {} | ||
| 34 | |||
| 35 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 36 | // Allocate a work buffer on stack. | ||
| 37 | alignas(DataAlignMax) std::array<char, DataAlign> work_buf; | ||
| 38 | |||
| 39 | // Succeed if zero size. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Validate arguments. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | s64 bs_size = this->GetSize(); | ||
| 48 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 49 | |||
| 50 | return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(), | ||
| 51 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 52 | } | ||
| 53 | |||
| 54 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 55 | // Allocate a work buffer on stack. | ||
| 56 | alignas(DataAlignMax) std::array<char, DataAlign> work_buf; | ||
| 57 | |||
| 58 | // Succeed if zero size. | ||
| 59 | if (size == 0) { | ||
| 60 | return size; | ||
| 61 | } | ||
| 62 | |||
| 63 | // Validate arguments. | ||
| 64 | ASSERT(buffer != nullptr); | ||
| 65 | |||
| 66 | s64 bs_size = this->GetSize(); | ||
| 67 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 68 | |||
| 69 | return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(), | ||
| 70 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 71 | } | ||
| 72 | |||
| 73 | virtual size_t GetSize() const override { | ||
| 74 | return m_base_storage->GetSize(); | ||
| 75 | } | ||
| 76 | }; | ||
| 77 | |||
| 78 | template <size_t BufferAlign_> | ||
| 79 | class AlignmentMatchingStoragePooledBuffer : public IStorage { | ||
| 80 | YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 81 | YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 82 | |||
| 83 | public: | ||
| 84 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 85 | |||
| 86 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 87 | |||
| 88 | private: | ||
| 89 | VirtualFile m_base_storage; | ||
| 90 | s64 m_base_storage_size; | ||
| 91 | size_t m_data_align; | ||
| 92 | |||
| 93 | public: | ||
| 94 | explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da) | ||
| 95 | : m_base_storage(std::move(bs)), m_data_align(da) { | ||
| 96 | ASSERT(Common::IsPowerOfTwo(da)); | ||
| 97 | } | ||
| 98 | |||
| 99 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 100 | // Succeed if zero size. | ||
| 101 | if (size == 0) { | ||
| 102 | return size; | ||
| 103 | } | ||
| 104 | |||
| 105 | // Validate arguments. | ||
| 106 | ASSERT(buffer != nullptr); | ||
| 107 | |||
| 108 | s64 bs_size = this->GetSize(); | ||
| 109 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 110 | |||
| 111 | // Allocate a pooled buffer. | ||
| 112 | PooledBuffer pooled_buffer; | ||
| 113 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 114 | |||
| 115 | return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 116 | pooled_buffer.GetSize(), m_data_align, | ||
| 117 | BufferAlign, offset, buffer, size); | ||
| 118 | } | ||
| 119 | |||
| 120 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 121 | // Succeed if zero size. | ||
| 122 | if (size == 0) { | ||
| 123 | return size; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Validate arguments. | ||
| 127 | ASSERT(buffer != nullptr); | ||
| 128 | |||
| 129 | s64 bs_size = this->GetSize(); | ||
| 130 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 131 | |||
| 132 | // Allocate a pooled buffer. | ||
| 133 | PooledBuffer pooled_buffer; | ||
| 134 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 135 | |||
| 136 | return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 137 | pooled_buffer.GetSize(), m_data_align, | ||
| 138 | BufferAlign, offset, buffer, size); | ||
| 139 | } | ||
| 140 | |||
| 141 | virtual size_t GetSize() const override { | ||
| 142 | return m_base_storage->GetSize(); | ||
| 143 | } | ||
| 144 | }; | ||
| 145 | |||
| 146 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 000000000..641c888ae --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | template <typename T> | ||
| 12 | constexpr size_t GetRoundDownDifference(T x, size_t align) { | ||
| 13 | return static_cast<size_t>(x - Common::AlignDown(x, align)); | ||
| 14 | } | ||
| 15 | |||
| 16 | template <typename T> | ||
| 17 | constexpr size_t GetRoundUpDifference(T x, size_t align) { | ||
| 18 | return static_cast<size_t>(Common::AlignUp(x, align) - x); | ||
| 19 | } | ||
| 20 | |||
| 21 | template <typename T> | ||
| 22 | size_t GetRoundUpDifference(T* x, size_t align) { | ||
| 23 | return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align); | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf, | ||
| 29 | size_t work_buf_size, size_t data_alignment, | ||
| 30 | size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 31 | size_t size) { | ||
| 32 | // Check preconditions. | ||
| 33 | ASSERT(work_buf_size >= data_alignment); | ||
| 34 | |||
| 35 | // Succeed if zero size. | ||
| 36 | if (size == 0) { | ||
| 37 | return size; | ||
| 38 | } | ||
| 39 | |||
| 40 | // Validate arguments. | ||
| 41 | ASSERT(buffer != nullptr); | ||
| 42 | |||
| 43 | // Determine extents. | ||
| 44 | u8* aligned_core_buffer; | ||
| 45 | s64 core_offset; | ||
| 46 | size_t core_size; | ||
| 47 | size_t buffer_gap; | ||
| 48 | size_t offset_gap; | ||
| 49 | s64 covered_offset; | ||
| 50 | |||
| 51 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 52 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 53 | buffer_alignment)) { | ||
| 54 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 55 | |||
| 56 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 57 | core_size = (size < offset_round_up_difference) | ||
| 58 | ? 0 | ||
| 59 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 60 | buffer_gap = 0; | ||
| 61 | offset_gap = 0; | ||
| 62 | |||
| 63 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 64 | } else { | ||
| 65 | const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); | ||
| 66 | |||
| 67 | aligned_core_buffer = buffer + buffer_round_up_difference; | ||
| 68 | |||
| 69 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 70 | core_size = (size < buffer_round_up_difference) | ||
| 71 | ? 0 | ||
| 72 | : Common::AlignDown(size - buffer_round_up_difference, data_alignment); | ||
| 73 | buffer_gap = buffer_round_up_difference; | ||
| 74 | offset_gap = GetRoundDownDifference(offset, data_alignment); | ||
| 75 | |||
| 76 | covered_offset = offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | // Read the core portion. | ||
| 80 | if (core_size > 0) { | ||
| 81 | base_storage->Read(aligned_core_buffer, core_size, core_offset); | ||
| 82 | |||
| 83 | if (offset_gap != 0 || buffer_gap != 0) { | ||
| 84 | std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, | ||
| 85 | core_size - offset_gap); | ||
| 86 | core_size -= offset_gap; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // Handle the head portion. | ||
| 91 | if (offset < covered_offset) { | ||
| 92 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 93 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 94 | |||
| 95 | ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); | ||
| 96 | |||
| 97 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 98 | std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); | ||
| 99 | } | ||
| 100 | |||
| 101 | // Handle the tail portion. | ||
| 102 | s64 tail_offset = covered_offset + core_size; | ||
| 103 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 104 | while (remaining_tail_size > 0) { | ||
| 105 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 106 | const auto cur_size = | ||
| 107 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 108 | remaining_tail_size); | ||
| 109 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 110 | |||
| 111 | ASSERT((tail_offset - offset) + cur_size <= size); | ||
| 112 | ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); | ||
| 113 | std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset), | ||
| 114 | work_buf + (tail_offset - aligned_tail_offset), cur_size); | ||
| 115 | |||
| 116 | remaining_tail_size -= cur_size; | ||
| 117 | tail_offset += cur_size; | ||
| 118 | } | ||
| 119 | |||
| 120 | return size; | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf, | ||
| 124 | size_t work_buf_size, size_t data_alignment, | ||
| 125 | size_t buffer_alignment, s64 offset, const u8* buffer, | ||
| 126 | size_t size) { | ||
| 127 | // Check preconditions. | ||
| 128 | ASSERT(work_buf_size >= data_alignment); | ||
| 129 | |||
| 130 | // Succeed if zero size. | ||
| 131 | if (size == 0) { | ||
| 132 | return size; | ||
| 133 | } | ||
| 134 | |||
| 135 | // Validate arguments. | ||
| 136 | ASSERT(buffer != nullptr); | ||
| 137 | |||
| 138 | // Determine extents. | ||
| 139 | const u8* aligned_core_buffer; | ||
| 140 | s64 core_offset; | ||
| 141 | size_t core_size; | ||
| 142 | s64 covered_offset; | ||
| 143 | |||
| 144 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 145 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 146 | buffer_alignment)) { | ||
| 147 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 148 | |||
| 149 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 150 | core_size = (size < offset_round_up_difference) | ||
| 151 | ? 0 | ||
| 152 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 153 | |||
| 154 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 155 | } else { | ||
| 156 | aligned_core_buffer = nullptr; | ||
| 157 | |||
| 158 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 159 | core_size = 0; | ||
| 160 | |||
| 161 | covered_offset = offset; | ||
| 162 | } | ||
| 163 | |||
| 164 | // Write the core portion. | ||
| 165 | if (core_size > 0) { | ||
| 166 | base_storage->Write(aligned_core_buffer, core_size, core_offset); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Handle the head portion. | ||
| 170 | if (offset < covered_offset) { | ||
| 171 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 172 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 173 | |||
| 174 | ASSERT((offset - head_offset) + head_size <= data_alignment); | ||
| 175 | |||
| 176 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 177 | std::memcpy(work_buf + (offset - head_offset), buffer, head_size); | ||
| 178 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 179 | } | ||
| 180 | |||
| 181 | // Handle the tail portion. | ||
| 182 | s64 tail_offset = covered_offset + core_size; | ||
| 183 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 184 | while (remaining_tail_size > 0) { | ||
| 185 | ASSERT(static_cast<size_t>(tail_offset - offset) < size); | ||
| 186 | |||
| 187 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 188 | const auto cur_size = | ||
| 189 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 190 | remaining_tail_size); | ||
| 191 | |||
| 192 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 193 | std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), | ||
| 194 | buffer + (tail_offset - offset), cur_size); | ||
| 195 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 196 | |||
| 197 | remaining_tail_size -= cur_size; | ||
| 198 | tail_offset += cur_size; | ||
| 199 | } | ||
| 200 | |||
| 201 | return size; | ||
| 202 | } | ||
| 203 | |||
| 204 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h new file mode 100644 index 000000000..4a05b0e88 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | class AlignmentMatchingStorageImpl { | ||
| 12 | public: | ||
| 13 | static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 14 | size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 15 | size_t size); | ||
| 16 | static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 17 | size_t data_alignment, size_t buffer_alignment, s64 offset, | ||
| 18 | const u8* buffer, size_t size); | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 000000000..af8541009 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp | |||
| @@ -0,0 +1,598 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | using Node = impl::BucketTreeNode<const s64*>; | ||
| 14 | static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); | ||
| 15 | static_assert(std::is_trivial_v<Node>); | ||
| 16 | |||
| 17 | constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); | ||
| 18 | |||
| 19 | class StorageNode { | ||
| 20 | private: | ||
| 21 | class Offset { | ||
| 22 | public: | ||
| 23 | using difference_type = s64; | ||
| 24 | |||
| 25 | private: | ||
| 26 | s64 m_offset; | ||
| 27 | s32 m_stride; | ||
| 28 | |||
| 29 | public: | ||
| 30 | constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {} | ||
| 31 | |||
| 32 | constexpr Offset& operator++() { | ||
| 33 | m_offset += m_stride; | ||
| 34 | return *this; | ||
| 35 | } | ||
| 36 | constexpr Offset operator++(int) { | ||
| 37 | Offset ret(*this); | ||
| 38 | m_offset += m_stride; | ||
| 39 | return ret; | ||
| 40 | } | ||
| 41 | |||
| 42 | constexpr Offset& operator--() { | ||
| 43 | m_offset -= m_stride; | ||
| 44 | return *this; | ||
| 45 | } | ||
| 46 | constexpr Offset operator--(int) { | ||
| 47 | Offset ret(*this); | ||
| 48 | m_offset -= m_stride; | ||
| 49 | return ret; | ||
| 50 | } | ||
| 51 | |||
| 52 | constexpr difference_type operator-(const Offset& rhs) const { | ||
| 53 | return (m_offset - rhs.m_offset) / m_stride; | ||
| 54 | } | ||
| 55 | |||
| 56 | constexpr Offset operator+(difference_type ofs) const { | ||
| 57 | return Offset(m_offset + ofs * m_stride, m_stride); | ||
| 58 | } | ||
| 59 | constexpr Offset operator-(difference_type ofs) const { | ||
| 60 | return Offset(m_offset - ofs * m_stride, m_stride); | ||
| 61 | } | ||
| 62 | |||
| 63 | constexpr Offset& operator+=(difference_type ofs) { | ||
| 64 | m_offset += ofs * m_stride; | ||
| 65 | return *this; | ||
| 66 | } | ||
| 67 | constexpr Offset& operator-=(difference_type ofs) { | ||
| 68 | m_offset -= ofs * m_stride; | ||
| 69 | return *this; | ||
| 70 | } | ||
| 71 | |||
| 72 | constexpr bool operator==(const Offset& rhs) const { | ||
| 73 | return m_offset == rhs.m_offset; | ||
| 74 | } | ||
| 75 | constexpr bool operator!=(const Offset& rhs) const { | ||
| 76 | return m_offset != rhs.m_offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | constexpr s64 Get() const { | ||
| 80 | return m_offset; | ||
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | private: | ||
| 85 | const Offset m_start; | ||
| 86 | const s32 m_count; | ||
| 87 | s32 m_index; | ||
| 88 | |||
| 89 | public: | ||
| 90 | StorageNode(size_t size, s32 count) | ||
| 91 | : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 92 | StorageNode(s64 ofs, size_t size, s32 count) | ||
| 93 | : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 94 | |||
| 95 | s32 GetIndex() const { | ||
| 96 | return m_index; | ||
| 97 | } | ||
| 98 | |||
| 99 | void Find(const char* buffer, s64 virtual_address) { | ||
| 100 | s32 end = m_count; | ||
| 101 | auto pos = m_start; | ||
| 102 | |||
| 103 | while (end > 0) { | ||
| 104 | auto half = end / 2; | ||
| 105 | auto mid = pos + half; | ||
| 106 | |||
| 107 | s64 offset = 0; | ||
| 108 | std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); | ||
| 109 | |||
| 110 | if (offset <= virtual_address) { | ||
| 111 | pos = mid + 1; | ||
| 112 | end -= half + 1; | ||
| 113 | } else { | ||
| 114 | end = half; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 119 | } | ||
| 120 | |||
| 121 | Result Find(VirtualFile storage, s64 virtual_address) { | ||
| 122 | s32 end = m_count; | ||
| 123 | auto pos = m_start; | ||
| 124 | |||
| 125 | while (end > 0) { | ||
| 126 | auto half = end / 2; | ||
| 127 | auto mid = pos + half; | ||
| 128 | |||
| 129 | s64 offset = 0; | ||
| 130 | storage->ReadObject(std::addressof(offset), mid.Get()); | ||
| 131 | |||
| 132 | if (offset <= virtual_address) { | ||
| 133 | pos = mid + 1; | ||
| 134 | end -= half + 1; | ||
| 135 | } else { | ||
| 136 | end = half; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 141 | R_SUCCEED(); | ||
| 142 | } | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace | ||
| 146 | |||
| 147 | void BucketTree::Header::Format(s32 entry_count_) { | ||
| 148 | ASSERT(entry_count_ >= 0); | ||
| 149 | |||
| 150 | this->magic = Magic; | ||
| 151 | this->version = Version; | ||
| 152 | this->entry_count = entry_count_; | ||
| 153 | this->reserved = 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | Result BucketTree::Header::Verify() const { | ||
| 157 | R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature); | ||
| 158 | R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount); | ||
| 159 | R_UNLESS(this->version <= Version, ResultUnsupportedVersion); | ||
| 160 | R_SUCCEED(); | ||
| 161 | } | ||
| 162 | |||
| 163 | Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { | ||
| 164 | R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex); | ||
| 165 | R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize); | ||
| 166 | |||
| 167 | const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; | ||
| 168 | R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, | ||
| 169 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 170 | R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset); | ||
| 171 | |||
| 172 | R_SUCCEED(); | ||
| 173 | } | ||
| 174 | |||
| 175 | Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 176 | size_t entry_size, s32 entry_count) { | ||
| 177 | // Validate preconditions. | ||
| 178 | ASSERT(entry_size >= sizeof(s64)); | ||
| 179 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 180 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 181 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 182 | ASSERT(!this->IsInitialized()); | ||
| 183 | |||
| 184 | // Ensure valid entry count. | ||
| 185 | R_UNLESS(entry_count > 0, ResultInvalidArgument); | ||
| 186 | |||
| 187 | // Allocate node. | ||
| 188 | R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed); | ||
| 189 | ON_RESULT_FAILURE { | ||
| 190 | m_node_l1.Free(node_size); | ||
| 191 | }; | ||
| 192 | |||
| 193 | // Read node. | ||
| 194 | node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size); | ||
| 195 | |||
| 196 | // Verify node. | ||
| 197 | R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64))); | ||
| 198 | |||
| 199 | // Validate offsets. | ||
| 200 | const auto offset_count = GetOffsetCount(node_size); | ||
| 201 | const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 202 | const auto* const node = m_node_l1.Get<Node>(); | ||
| 203 | |||
| 204 | s64 start_offset; | ||
| 205 | if (offset_count < entry_set_count && node->GetCount() < offset_count) { | ||
| 206 | start_offset = *node->GetEnd(); | ||
| 207 | } else { | ||
| 208 | start_offset = *node->GetBegin(); | ||
| 209 | } | ||
| 210 | const auto end_offset = node->GetEndOffset(); | ||
| 211 | |||
| 212 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 213 | ResultInvalidBucketTreeEntryOffset); | ||
| 214 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 215 | |||
| 216 | // Set member variables. | ||
| 217 | m_node_storage = node_storage; | ||
| 218 | m_entry_storage = entry_storage; | ||
| 219 | m_node_size = node_size; | ||
| 220 | m_entry_size = entry_size; | ||
| 221 | m_entry_count = entry_count; | ||
| 222 | m_offset_count = offset_count; | ||
| 223 | m_entry_set_count = entry_set_count; | ||
| 224 | |||
| 225 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 226 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 227 | m_offset_cache.is_initialized = true; | ||
| 228 | |||
| 229 | // We succeeded. | ||
| 230 | R_SUCCEED(); | ||
| 231 | } | ||
| 232 | |||
| 233 | void BucketTree::Initialize(size_t node_size, s64 end_offset) { | ||
| 234 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 235 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 236 | ASSERT(end_offset > 0); | ||
| 237 | ASSERT(!this->IsInitialized()); | ||
| 238 | |||
| 239 | m_node_size = node_size; | ||
| 240 | |||
| 241 | m_offset_cache.offsets.start_offset = 0; | ||
| 242 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 243 | m_offset_cache.is_initialized = true; | ||
| 244 | } | ||
| 245 | |||
| 246 | void BucketTree::Finalize() { | ||
| 247 | if (this->IsInitialized()) { | ||
| 248 | m_node_storage = VirtualFile(); | ||
| 249 | m_entry_storage = VirtualFile(); | ||
| 250 | m_node_l1.Free(m_node_size); | ||
| 251 | m_node_size = 0; | ||
| 252 | m_entry_size = 0; | ||
| 253 | m_entry_count = 0; | ||
| 254 | m_offset_count = 0; | ||
| 255 | m_entry_set_count = 0; | ||
| 256 | |||
| 257 | m_offset_cache.offsets.start_offset = 0; | ||
| 258 | m_offset_cache.offsets.end_offset = 0; | ||
| 259 | m_offset_cache.is_initialized = false; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | Result BucketTree::Find(Visitor* visitor, s64 virtual_address) { | ||
| 264 | ASSERT(visitor != nullptr); | ||
| 265 | ASSERT(this->IsInitialized()); | ||
| 266 | |||
| 267 | R_UNLESS(virtual_address >= 0, ResultInvalidOffset); | ||
| 268 | R_UNLESS(!this->IsEmpty(), ResultOutOfRange); | ||
| 269 | |||
| 270 | BucketTree::Offsets offsets; | ||
| 271 | R_TRY(this->GetOffsets(std::addressof(offsets))); | ||
| 272 | |||
| 273 | R_TRY(visitor->Initialize(this, offsets)); | ||
| 274 | |||
| 275 | R_RETURN(visitor->Find(virtual_address)); | ||
| 276 | } | ||
| 277 | |||
| 278 | Result BucketTree::InvalidateCache() { | ||
| 279 | // Reset our offsets. | ||
| 280 | m_offset_cache.is_initialized = false; | ||
| 281 | |||
| 282 | R_SUCCEED(); | ||
| 283 | } | ||
| 284 | |||
| 285 | Result BucketTree::EnsureOffsetCache() { | ||
| 286 | // If we already have an offset cache, we're good. | ||
| 287 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 288 | |||
| 289 | // Acquire exclusive right to edit the offset cache. | ||
| 290 | std::scoped_lock lk(m_offset_cache.mutex); | ||
| 291 | |||
| 292 | // Check again, to be sure. | ||
| 293 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 294 | |||
| 295 | // Read/verify L1. | ||
| 296 | m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size); | ||
| 297 | R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64))); | ||
| 298 | |||
| 299 | // Get the node. | ||
| 300 | auto* const node = m_node_l1.Get<Node>(); | ||
| 301 | |||
| 302 | s64 start_offset; | ||
| 303 | if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) { | ||
| 304 | start_offset = *node->GetEnd(); | ||
| 305 | } else { | ||
| 306 | start_offset = *node->GetBegin(); | ||
| 307 | } | ||
| 308 | const auto end_offset = node->GetEndOffset(); | ||
| 309 | |||
| 310 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 311 | ResultInvalidBucketTreeEntryOffset); | ||
| 312 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 313 | |||
| 314 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 315 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 316 | m_offset_cache.is_initialized = true; | ||
| 317 | |||
| 318 | R_SUCCEED(); | ||
| 319 | } | ||
| 320 | |||
| 321 | Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) { | ||
| 322 | ASSERT(tree != nullptr); | ||
| 323 | ASSERT(m_tree == nullptr || m_tree == tree); | ||
| 324 | |||
| 325 | if (m_entry == nullptr) { | ||
| 326 | m_entry = ::operator new(tree->m_entry_size); | ||
| 327 | R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed); | ||
| 328 | |||
| 329 | m_tree = tree; | ||
| 330 | m_offsets = offsets; | ||
| 331 | } | ||
| 332 | |||
| 333 | R_SUCCEED(); | ||
| 334 | } | ||
| 335 | |||
| 336 | Result BucketTree::Visitor::MoveNext() { | ||
| 337 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 338 | |||
| 339 | // Invalidate our index, and read the header for the next index. | ||
| 340 | auto entry_index = m_entry_index + 1; | ||
| 341 | if (entry_index == m_entry_set.info.count) { | ||
| 342 | const auto entry_set_index = m_entry_set.info.index + 1; | ||
| 343 | R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange); | ||
| 344 | |||
| 345 | m_entry_index = -1; | ||
| 346 | |||
| 347 | const auto end = m_entry_set.info.end; | ||
| 348 | |||
| 349 | const auto entry_set_size = m_tree->m_node_size; | ||
| 350 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 351 | |||
| 352 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 353 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 354 | |||
| 355 | R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, | ||
| 356 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 357 | |||
| 358 | entry_index = 0; | ||
| 359 | } else { | ||
| 360 | m_entry_index = -1; | ||
| 361 | } | ||
| 362 | |||
| 363 | // Read the new entry. | ||
| 364 | const auto entry_size = m_tree->m_entry_size; | ||
| 365 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 366 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 367 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 368 | |||
| 369 | // Note that we changed index. | ||
| 370 | m_entry_index = entry_index; | ||
| 371 | R_SUCCEED(); | ||
| 372 | } | ||
| 373 | |||
| 374 | Result BucketTree::Visitor::MovePrevious() { | ||
| 375 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 376 | |||
| 377 | // Invalidate our index, and read the header for the previous index. | ||
| 378 | auto entry_index = m_entry_index; | ||
| 379 | if (entry_index == 0) { | ||
| 380 | R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange); | ||
| 381 | |||
| 382 | m_entry_index = -1; | ||
| 383 | |||
| 384 | const auto start = m_entry_set.info.start; | ||
| 385 | |||
| 386 | const auto entry_set_size = m_tree->m_node_size; | ||
| 387 | const auto entry_set_index = m_entry_set.info.index - 1; | ||
| 388 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 389 | |||
| 390 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 391 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 392 | |||
| 393 | R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, | ||
| 394 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 395 | |||
| 396 | entry_index = m_entry_set.info.count; | ||
| 397 | } else { | ||
| 398 | m_entry_index = -1; | ||
| 399 | } | ||
| 400 | |||
| 401 | --entry_index; | ||
| 402 | |||
| 403 | // Read the new entry. | ||
| 404 | const auto entry_size = m_tree->m_entry_size; | ||
| 405 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 406 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 407 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 408 | |||
| 409 | // Note that we changed index. | ||
| 410 | m_entry_index = entry_index; | ||
| 411 | R_SUCCEED(); | ||
| 412 | } | ||
| 413 | |||
| 414 | Result BucketTree::Visitor::Find(s64 virtual_address) { | ||
| 415 | ASSERT(m_tree != nullptr); | ||
| 416 | |||
| 417 | // Get the node. | ||
| 418 | const auto* const node = m_tree->m_node_l1.Get<Node>(); | ||
| 419 | R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange); | ||
| 420 | |||
| 421 | // Get the entry set index. | ||
| 422 | s32 entry_set_index = -1; | ||
| 423 | if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { | ||
| 424 | const auto start = node->GetEnd(); | ||
| 425 | const auto end = node->GetBegin() + m_tree->m_offset_count; | ||
| 426 | |||
| 427 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 428 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 429 | --pos; | ||
| 430 | |||
| 431 | entry_set_index = static_cast<s32>(pos - start); | ||
| 432 | } else { | ||
| 433 | const auto start = node->GetBegin(); | ||
| 434 | const auto end = node->GetEnd(); | ||
| 435 | |||
| 436 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 437 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 438 | --pos; | ||
| 439 | |||
| 440 | if (m_tree->IsExistL2()) { | ||
| 441 | const auto node_index = static_cast<s32>(pos - start); | ||
| 442 | R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, | ||
| 443 | ResultInvalidBucketTreeNodeOffset); | ||
| 444 | |||
| 445 | R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); | ||
| 446 | } else { | ||
| 447 | entry_set_index = static_cast<s32>(pos - start); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | // Validate the entry set index. | ||
| 452 | R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, | ||
| 453 | ResultInvalidBucketTreeNodeOffset); | ||
| 454 | |||
| 455 | // Find the entry. | ||
| 456 | R_TRY(this->FindEntry(virtual_address, entry_set_index)); | ||
| 457 | |||
| 458 | // Set count. | ||
| 459 | m_entry_set_count = m_tree->m_entry_set_count; | ||
| 460 | R_SUCCEED(); | ||
| 461 | } | ||
| 462 | |||
| 463 | Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) { | ||
| 464 | const auto node_size = m_tree->m_node_size; | ||
| 465 | |||
| 466 | PooledBuffer pool(node_size, 1); | ||
| 467 | if (node_size <= pool.GetSize()) { | ||
| 468 | R_RETURN( | ||
| 469 | this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer())); | ||
| 470 | } else { | ||
| 471 | pool.Deallocate(); | ||
| 472 | R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index)); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, | ||
| 477 | s32 node_index, char* buffer) { | ||
| 478 | // Calculate node extents. | ||
| 479 | const auto node_size = m_tree->m_node_size; | ||
| 480 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 481 | VirtualFile storage = m_tree->m_node_storage; | ||
| 482 | |||
| 483 | // Read the node. | ||
| 484 | storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset); | ||
| 485 | |||
| 486 | // Validate the header. | ||
| 487 | NodeHeader header; | ||
| 488 | std::memcpy(std::addressof(header), buffer, NodeHeaderSize); | ||
| 489 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 490 | |||
| 491 | // Create the node, and find. | ||
| 492 | StorageNode node(sizeof(s64), header.count); | ||
| 493 | node.Find(buffer, virtual_address); | ||
| 494 | R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset); | ||
| 495 | |||
| 496 | // Return the index. | ||
| 497 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 498 | R_SUCCEED(); | ||
| 499 | } | ||
| 500 | |||
| 501 | Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, | ||
| 502 | s32 node_index) { | ||
| 503 | // Calculate node extents. | ||
| 504 | const auto node_size = m_tree->m_node_size; | ||
| 505 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 506 | VirtualFile storage = m_tree->m_node_storage; | ||
| 507 | |||
| 508 | // Read and validate the header. | ||
| 509 | NodeHeader header; | ||
| 510 | storage->ReadObject(std::addressof(header), node_offset); | ||
| 511 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 512 | |||
| 513 | // Create the node, and find. | ||
| 514 | StorageNode node(node_offset, sizeof(s64), header.count); | ||
| 515 | R_TRY(node.Find(storage, virtual_address)); | ||
| 516 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 517 | |||
| 518 | // Return the index. | ||
| 519 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { | ||
| 524 | const auto entry_set_size = m_tree->m_node_size; | ||
| 525 | |||
| 526 | PooledBuffer pool(entry_set_size, 1); | ||
| 527 | if (entry_set_size <= pool.GetSize()) { | ||
| 528 | R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer())); | ||
| 529 | } else { | ||
| 530 | pool.Deallocate(); | ||
| 531 | R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index)); | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, | ||
| 536 | char* buffer) { | ||
| 537 | // Calculate entry set extents. | ||
| 538 | const auto entry_size = m_tree->m_entry_size; | ||
| 539 | const auto entry_set_size = m_tree->m_node_size; | ||
| 540 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 541 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 542 | |||
| 543 | // Read the entry set. | ||
| 544 | storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset); | ||
| 545 | |||
| 546 | // Validate the entry_set. | ||
| 547 | EntrySetHeader entry_set; | ||
| 548 | std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); | ||
| 549 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 550 | |||
| 551 | // Create the node, and find. | ||
| 552 | StorageNode node(entry_size, entry_set.info.count); | ||
| 553 | node.Find(buffer, virtual_address); | ||
| 554 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 555 | |||
| 556 | // Copy the data into entry. | ||
| 557 | const auto entry_index = node.GetIndex(); | ||
| 558 | const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); | ||
| 559 | std::memcpy(m_entry, buffer + entry_offset, entry_size); | ||
| 560 | |||
| 561 | // Set our entry set/index. | ||
| 562 | m_entry_set = entry_set; | ||
| 563 | m_entry_index = entry_index; | ||
| 564 | |||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { | ||
| 569 | // Calculate entry set extents. | ||
| 570 | const auto entry_size = m_tree->m_entry_size; | ||
| 571 | const auto entry_set_size = m_tree->m_node_size; | ||
| 572 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 573 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 574 | |||
| 575 | // Read and validate the entry_set. | ||
| 576 | EntrySetHeader entry_set; | ||
| 577 | storage->ReadObject(std::addressof(entry_set), entry_set_offset); | ||
| 578 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 579 | |||
| 580 | // Create the node, and find. | ||
| 581 | StorageNode node(entry_set_offset, entry_size, entry_set.info.count); | ||
| 582 | R_TRY(node.Find(storage, virtual_address)); | ||
| 583 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 584 | |||
| 585 | // Copy the data into entry. | ||
| 586 | const auto entry_index = node.GetIndex(); | ||
| 587 | const auto entry_offset = | ||
| 588 | impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); | ||
| 589 | storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 590 | |||
| 591 | // Set our entry set/index. | ||
| 592 | m_entry_set = entry_set; | ||
| 593 | m_entry_index = entry_index; | ||
| 594 | |||
| 595 | R_SUCCEED(); | ||
| 596 | } | ||
| 597 | |||
| 598 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h new file mode 100644 index 000000000..46850cd48 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h | |||
| @@ -0,0 +1,416 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "common/alignment.h" | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/literals.h" | ||
| 12 | |||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | #include "core/hle/result.h" | ||
| 15 | |||
| 16 | namespace FileSys { | ||
| 17 | |||
| 18 | using namespace Common::Literals; | ||
| 19 | |||
| 20 | class BucketTree { | ||
| 21 | YUZU_NON_COPYABLE(BucketTree); | ||
| 22 | YUZU_NON_MOVEABLE(BucketTree); | ||
| 23 | |||
| 24 | public: | ||
| 25 | static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R'); | ||
| 26 | static constexpr u32 Version = 1; | ||
| 27 | |||
| 28 | static constexpr size_t NodeSizeMin = 1_KiB; | ||
| 29 | static constexpr size_t NodeSizeMax = 512_KiB; | ||
| 30 | |||
| 31 | public: | ||
| 32 | class Visitor; | ||
| 33 | |||
| 34 | struct Header { | ||
| 35 | u32 magic; | ||
| 36 | u32 version; | ||
| 37 | s32 entry_count; | ||
| 38 | s32 reserved; | ||
| 39 | |||
| 40 | void Format(s32 entry_count); | ||
| 41 | Result Verify() const; | ||
| 42 | }; | ||
| 43 | static_assert(std::is_trivial_v<Header>); | ||
| 44 | static_assert(sizeof(Header) == 0x10); | ||
| 45 | |||
| 46 | struct NodeHeader { | ||
| 47 | s32 index; | ||
| 48 | s32 count; | ||
| 49 | s64 offset; | ||
| 50 | |||
| 51 | Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; | ||
| 52 | }; | ||
| 53 | static_assert(std::is_trivial_v<NodeHeader>); | ||
| 54 | static_assert(sizeof(NodeHeader) == 0x10); | ||
| 55 | |||
| 56 | struct Offsets { | ||
| 57 | s64 start_offset; | ||
| 58 | s64 end_offset; | ||
| 59 | |||
| 60 | constexpr bool IsInclude(s64 offset) const { | ||
| 61 | return this->start_offset <= offset && offset < this->end_offset; | ||
| 62 | } | ||
| 63 | |||
| 64 | constexpr bool IsInclude(s64 offset, s64 size) const { | ||
| 65 | return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | static_assert(std::is_trivial_v<Offsets>); | ||
| 69 | static_assert(sizeof(Offsets) == 0x10); | ||
| 70 | |||
| 71 | struct OffsetCache { | ||
| 72 | Offsets offsets; | ||
| 73 | std::mutex mutex; | ||
| 74 | bool is_initialized; | ||
| 75 | |||
| 76 | OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {} | ||
| 77 | }; | ||
| 78 | |||
| 79 | class ContinuousReadingInfo { | ||
| 80 | public: | ||
| 81 | constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {} | ||
| 82 | |||
| 83 | constexpr void Reset() { | ||
| 84 | m_read_size = 0; | ||
| 85 | m_skip_count = 0; | ||
| 86 | m_done = false; | ||
| 87 | } | ||
| 88 | |||
| 89 | constexpr void SetSkipCount(s32 count) { | ||
| 90 | ASSERT(count >= 0); | ||
| 91 | m_skip_count = count; | ||
| 92 | } | ||
| 93 | constexpr s32 GetSkipCount() const { | ||
| 94 | return m_skip_count; | ||
| 95 | } | ||
| 96 | constexpr bool CheckNeedScan() { | ||
| 97 | return (--m_skip_count) <= 0; | ||
| 98 | } | ||
| 99 | |||
| 100 | constexpr void Done() { | ||
| 101 | m_read_size = 0; | ||
| 102 | m_done = true; | ||
| 103 | } | ||
| 104 | constexpr bool IsDone() const { | ||
| 105 | return m_done; | ||
| 106 | } | ||
| 107 | |||
| 108 | constexpr void SetReadSize(size_t size) { | ||
| 109 | m_read_size = size; | ||
| 110 | } | ||
| 111 | constexpr size_t GetReadSize() const { | ||
| 112 | return m_read_size; | ||
| 113 | } | ||
| 114 | constexpr bool CanDo() const { | ||
| 115 | return m_read_size > 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | private: | ||
| 119 | size_t m_read_size; | ||
| 120 | s32 m_skip_count; | ||
| 121 | bool m_done; | ||
| 122 | }; | ||
| 123 | |||
| 124 | private: | ||
| 125 | class NodeBuffer { | ||
| 126 | YUZU_NON_COPYABLE(NodeBuffer); | ||
| 127 | |||
| 128 | public: | ||
| 129 | NodeBuffer() : m_header() {} | ||
| 130 | |||
| 131 | ~NodeBuffer() { | ||
| 132 | ASSERT(m_header == nullptr); | ||
| 133 | } | ||
| 134 | |||
| 135 | NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) { | ||
| 136 | rhs.m_header = nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | NodeBuffer& operator=(NodeBuffer&& rhs) { | ||
| 140 | if (this != std::addressof(rhs)) { | ||
| 141 | ASSERT(m_header == nullptr); | ||
| 142 | |||
| 143 | m_header = rhs.m_header; | ||
| 144 | |||
| 145 | rhs.m_header = nullptr; | ||
| 146 | } | ||
| 147 | return *this; | ||
| 148 | } | ||
| 149 | |||
| 150 | bool Allocate(size_t node_size) { | ||
| 151 | ASSERT(m_header == nullptr); | ||
| 152 | |||
| 153 | m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)}); | ||
| 154 | |||
| 155 | // ASSERT(Common::IsAligned(m_header, sizeof(s64))); | ||
| 156 | |||
| 157 | return m_header != nullptr; | ||
| 158 | } | ||
| 159 | |||
| 160 | void Free(size_t node_size) { | ||
| 161 | if (m_header) { | ||
| 162 | ::operator delete(m_header, std::align_val_t{sizeof(s64)}); | ||
| 163 | m_header = nullptr; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | void FillZero(size_t node_size) const { | ||
| 168 | if (m_header) { | ||
| 169 | std::memset(m_header, 0, node_size); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | NodeHeader* Get() const { | ||
| 174 | return reinterpret_cast<NodeHeader*>(m_header); | ||
| 175 | } | ||
| 176 | |||
| 177 | NodeHeader* operator->() const { | ||
| 178 | return this->Get(); | ||
| 179 | } | ||
| 180 | |||
| 181 | template <typename T> | ||
| 182 | T* Get() const { | ||
| 183 | static_assert(std::is_trivial_v<T>); | ||
| 184 | static_assert(sizeof(T) == sizeof(NodeHeader)); | ||
| 185 | return reinterpret_cast<T*>(m_header); | ||
| 186 | } | ||
| 187 | |||
| 188 | private: | ||
| 189 | void* m_header; | ||
| 190 | }; | ||
| 191 | |||
| 192 | private: | ||
| 193 | static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { | ||
| 194 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size); | ||
| 195 | } | ||
| 196 | |||
| 197 | static constexpr s32 GetOffsetCount(size_t node_size) { | ||
| 198 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64)); | ||
| 199 | } | ||
| 200 | |||
| 201 | static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 202 | const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); | ||
| 203 | return Common::DivideUp(entry_count, entry_count_per_node); | ||
| 204 | } | ||
| 205 | |||
| 206 | static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 207 | const s32 offset_count_per_node = GetOffsetCount(node_size); | ||
| 208 | const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 209 | |||
| 210 | if (entry_set_count <= offset_count_per_node) { | ||
| 211 | return 0; | ||
| 212 | } | ||
| 213 | |||
| 214 | const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node); | ||
| 215 | ASSERT(node_l2_count <= offset_count_per_node); | ||
| 216 | |||
| 217 | return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), | ||
| 218 | offset_count_per_node); | ||
| 219 | } | ||
| 220 | |||
| 221 | public: | ||
| 222 | BucketTree() | ||
| 223 | : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(), | ||
| 224 | m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {} | ||
| 225 | ~BucketTree() { | ||
| 226 | this->Finalize(); | ||
| 227 | } | ||
| 228 | |||
| 229 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 230 | size_t entry_size, s32 entry_count); | ||
| 231 | void Initialize(size_t node_size, s64 end_offset); | ||
| 232 | void Finalize(); | ||
| 233 | |||
| 234 | bool IsInitialized() const { | ||
| 235 | return m_node_size > 0; | ||
| 236 | } | ||
| 237 | bool IsEmpty() const { | ||
| 238 | return m_entry_size == 0; | ||
| 239 | } | ||
| 240 | |||
| 241 | Result Find(Visitor* visitor, s64 virtual_address); | ||
| 242 | Result InvalidateCache(); | ||
| 243 | |||
| 244 | s32 GetEntryCount() const { | ||
| 245 | return m_entry_count; | ||
| 246 | } | ||
| 247 | |||
| 248 | Result GetOffsets(Offsets* out) { | ||
| 249 | // Ensure we have an offset cache. | ||
| 250 | R_TRY(this->EnsureOffsetCache()); | ||
| 251 | |||
| 252 | // Set the output. | ||
| 253 | *out = m_offset_cache.offsets; | ||
| 254 | R_SUCCEED(); | ||
| 255 | } | ||
| 256 | |||
| 257 | public: | ||
| 258 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 259 | return sizeof(Header); | ||
| 260 | } | ||
| 261 | |||
| 262 | static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, | ||
| 263 | s32 entry_count) { | ||
| 264 | ASSERT(entry_size >= sizeof(s64)); | ||
| 265 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 266 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 267 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 268 | ASSERT(entry_count >= 0); | ||
| 269 | |||
| 270 | if (entry_count <= 0) { | ||
| 271 | return 0; | ||
| 272 | } | ||
| 273 | return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * | ||
| 274 | static_cast<s64>(node_size); | ||
| 275 | } | ||
| 276 | |||
| 277 | static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, | ||
| 278 | s32 entry_count) { | ||
| 279 | ASSERT(entry_size >= sizeof(s64)); | ||
| 280 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 281 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 282 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 283 | ASSERT(entry_count >= 0); | ||
| 284 | |||
| 285 | if (entry_count <= 0) { | ||
| 286 | return 0; | ||
| 287 | } | ||
| 288 | return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size); | ||
| 289 | } | ||
| 290 | |||
| 291 | private: | ||
| 292 | template <typename EntryType> | ||
| 293 | struct ContinuousReadingParam { | ||
| 294 | s64 offset; | ||
| 295 | size_t size; | ||
| 296 | NodeHeader entry_set; | ||
| 297 | s32 entry_index; | ||
| 298 | Offsets offsets; | ||
| 299 | EntryType entry; | ||
| 300 | }; | ||
| 301 | |||
| 302 | private: | ||
| 303 | template <typename EntryType> | ||
| 304 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 305 | const ContinuousReadingParam<EntryType>& param) const; | ||
| 306 | |||
| 307 | bool IsExistL2() const { | ||
| 308 | return m_offset_count < m_entry_set_count; | ||
| 309 | } | ||
| 310 | bool IsExistOffsetL2OnL1() const { | ||
| 311 | return this->IsExistL2() && m_node_l1->count < m_offset_count; | ||
| 312 | } | ||
| 313 | |||
| 314 | s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { | ||
| 315 | return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index; | ||
| 316 | } | ||
| 317 | |||
| 318 | Result EnsureOffsetCache(); | ||
| 319 | |||
| 320 | private: | ||
| 321 | mutable VirtualFile m_node_storage; | ||
| 322 | mutable VirtualFile m_entry_storage; | ||
| 323 | NodeBuffer m_node_l1; | ||
| 324 | size_t m_node_size; | ||
| 325 | size_t m_entry_size; | ||
| 326 | s32 m_entry_count; | ||
| 327 | s32 m_offset_count; | ||
| 328 | s32 m_entry_set_count; | ||
| 329 | OffsetCache m_offset_cache; | ||
| 330 | }; | ||
| 331 | |||
| 332 | class BucketTree::Visitor { | ||
| 333 | YUZU_NON_COPYABLE(Visitor); | ||
| 334 | YUZU_NON_MOVEABLE(Visitor); | ||
| 335 | |||
| 336 | public: | ||
| 337 | constexpr Visitor() | ||
| 338 | : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {} | ||
| 339 | ~Visitor() { | ||
| 340 | if (m_entry != nullptr) { | ||
| 341 | ::operator delete(m_entry, m_tree->m_entry_size); | ||
| 342 | m_tree = nullptr; | ||
| 343 | m_entry = nullptr; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | bool IsValid() const { | ||
| 348 | return m_entry_index >= 0; | ||
| 349 | } | ||
| 350 | bool CanMoveNext() const { | ||
| 351 | return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count || | ||
| 352 | m_entry_set.info.index + 1 < m_entry_set_count); | ||
| 353 | } | ||
| 354 | bool CanMovePrevious() const { | ||
| 355 | return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0); | ||
| 356 | } | ||
| 357 | |||
| 358 | Result MoveNext(); | ||
| 359 | Result MovePrevious(); | ||
| 360 | |||
| 361 | template <typename EntryType> | ||
| 362 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const; | ||
| 363 | |||
| 364 | const void* Get() const { | ||
| 365 | ASSERT(this->IsValid()); | ||
| 366 | return m_entry; | ||
| 367 | } | ||
| 368 | |||
| 369 | template <typename T> | ||
| 370 | const T* Get() const { | ||
| 371 | ASSERT(this->IsValid()); | ||
| 372 | return reinterpret_cast<const T*>(m_entry); | ||
| 373 | } | ||
| 374 | |||
| 375 | const BucketTree* GetTree() const { | ||
| 376 | return m_tree; | ||
| 377 | } | ||
| 378 | |||
| 379 | private: | ||
| 380 | Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets); | ||
| 381 | |||
| 382 | Result Find(s64 virtual_address); | ||
| 383 | |||
| 384 | Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 385 | Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index, | ||
| 386 | char* buffer); | ||
| 387 | Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 388 | |||
| 389 | Result FindEntry(s64 virtual_address, s32 entry_set_index); | ||
| 390 | Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer); | ||
| 391 | Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); | ||
| 392 | |||
| 393 | private: | ||
| 394 | friend class BucketTree; | ||
| 395 | |||
| 396 | union EntrySetHeader { | ||
| 397 | NodeHeader header; | ||
| 398 | struct Info { | ||
| 399 | s32 index; | ||
| 400 | s32 count; | ||
| 401 | s64 end; | ||
| 402 | s64 start; | ||
| 403 | } info; | ||
| 404 | static_assert(std::is_trivial_v<Info>); | ||
| 405 | }; | ||
| 406 | static_assert(std::is_trivial_v<EntrySetHeader>); | ||
| 407 | |||
| 408 | const BucketTree* m_tree; | ||
| 409 | BucketTree::Offsets m_offsets; | ||
| 410 | void* m_entry; | ||
| 411 | s32 m_entry_index; | ||
| 412 | s32 m_entry_set_count; | ||
| 413 | EntrySetHeader m_entry_set; | ||
| 414 | }; | ||
| 415 | |||
| 416 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h new file mode 100644 index 000000000..030b2916b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | template <typename EntryType> | ||
| 14 | Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 15 | const ContinuousReadingParam<EntryType>& param) const { | ||
| 16 | static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>); | ||
| 17 | |||
| 18 | // Validate our preconditions. | ||
| 19 | ASSERT(this->IsInitialized()); | ||
| 20 | ASSERT(out_info != nullptr); | ||
| 21 | ASSERT(m_entry_size == sizeof(EntryType)); | ||
| 22 | |||
| 23 | // Reset the output. | ||
| 24 | out_info->Reset(); | ||
| 25 | |||
| 26 | // If there's nothing to read, we're done. | ||
| 27 | R_SUCCEED_IF(param.size == 0); | ||
| 28 | |||
| 29 | // If we're reading a fragment, we're done. | ||
| 30 | R_SUCCEED_IF(param.entry.IsFragment()); | ||
| 31 | |||
| 32 | // Validate the first entry. | ||
| 33 | auto entry = param.entry; | ||
| 34 | auto cur_offset = param.offset; | ||
| 35 | R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange); | ||
| 36 | |||
| 37 | // Create a pooled buffer for our scan. | ||
| 38 | PooledBuffer pool(m_node_size, 1); | ||
| 39 | char* buffer = nullptr; | ||
| 40 | |||
| 41 | s64 entry_storage_size = m_entry_storage->GetSize(); | ||
| 42 | |||
| 43 | // Read the node. | ||
| 44 | if (m_node_size <= pool.GetSize()) { | ||
| 45 | buffer = pool.GetBuffer(); | ||
| 46 | const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size); | ||
| 47 | R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size), | ||
| 48 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 49 | |||
| 50 | m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs); | ||
| 51 | } | ||
| 52 | |||
| 53 | // Calculate extents. | ||
| 54 | const auto end_offset = cur_offset + static_cast<s64>(param.size); | ||
| 55 | s64 phys_offset = entry.GetPhysicalOffset(); | ||
| 56 | |||
| 57 | // Start merge tracking. | ||
| 58 | s64 merge_size = 0; | ||
| 59 | s64 readable_size = 0; | ||
| 60 | bool merged = false; | ||
| 61 | |||
| 62 | // Iterate. | ||
| 63 | auto entry_index = param.entry_index; | ||
| 64 | for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { | ||
| 65 | // If we're past the end, we're done. | ||
| 66 | if (end_offset <= cur_offset) { | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | |||
| 70 | // Validate the entry offset. | ||
| 71 | const auto entry_offset = entry.GetVirtualOffset(); | ||
| 72 | R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 73 | |||
| 74 | // Get the next entry. | ||
| 75 | EntryType next_entry = {}; | ||
| 76 | s64 next_entry_offset; | ||
| 77 | |||
| 78 | if (entry_index + 1 < entry_count) { | ||
| 79 | if (buffer != nullptr) { | ||
| 80 | const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1); | ||
| 81 | std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size); | ||
| 82 | } else { | ||
| 83 | const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size, | ||
| 84 | m_entry_size, entry_index + 1); | ||
| 85 | m_entry_storage->ReadObject(std::addressof(next_entry), ofs); | ||
| 86 | } | ||
| 87 | |||
| 88 | next_entry_offset = next_entry.GetVirtualOffset(); | ||
| 89 | R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 90 | } else { | ||
| 91 | next_entry_offset = param.entry_set.offset; | ||
| 92 | } | ||
| 93 | |||
| 94 | // Validate the next entry offset. | ||
| 95 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 96 | |||
| 97 | // Determine the much data there is. | ||
| 98 | const auto data_size = next_entry_offset - cur_offset; | ||
| 99 | ASSERT(data_size > 0); | ||
| 100 | |||
| 101 | // Determine how much data we should read. | ||
| 102 | const auto remaining_size = end_offset - cur_offset; | ||
| 103 | const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size)); | ||
| 104 | ASSERT(read_size <= param.size); | ||
| 105 | |||
| 106 | // Update our merge tracking. | ||
| 107 | if (entry.IsFragment()) { | ||
| 108 | // If we can't merge, stop looping. | ||
| 109 | if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { | ||
| 110 | break; | ||
| 111 | } | ||
| 112 | |||
| 113 | // Otherwise, add the current size to the merge size. | ||
| 114 | merge_size += read_size; | ||
| 115 | } else { | ||
| 116 | // If we can't merge, stop looping. | ||
| 117 | if (phys_offset != entry.GetPhysicalOffset()) { | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Add the size to the readable amount. | ||
| 122 | readable_size += merge_size + read_size; | ||
| 123 | ASSERT(readable_size <= static_cast<s64>(param.size)); | ||
| 124 | |||
| 125 | // Update whether we've merged. | ||
| 126 | merged |= merge_size > 0; | ||
| 127 | merge_size = 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | // Advance. | ||
| 131 | cur_offset += read_size; | ||
| 132 | ASSERT(cur_offset <= end_offset); | ||
| 133 | |||
| 134 | phys_offset += next_entry_offset - entry_offset; | ||
| 135 | entry = next_entry; | ||
| 136 | } | ||
| 137 | |||
| 138 | // If we merged, set our readable size. | ||
| 139 | if (merged) { | ||
| 140 | out_info->SetReadSize(static_cast<size_t>(readable_size)); | ||
| 141 | } | ||
| 142 | out_info->SetSkipCount(entry_index - param.entry_index); | ||
| 143 | |||
| 144 | R_SUCCEED(); | ||
| 145 | } | ||
| 146 | |||
| 147 | template <typename EntryType> | ||
| 148 | Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, | ||
| 149 | size_t size) const { | ||
| 150 | static_assert(std::is_trivial_v<EntryType>); | ||
| 151 | ASSERT(this->IsValid()); | ||
| 152 | |||
| 153 | // Create our parameters. | ||
| 154 | ContinuousReadingParam<EntryType> param = { | ||
| 155 | .offset = offset, | ||
| 156 | .size = size, | ||
| 157 | .entry_set = m_entry_set.header, | ||
| 158 | .entry_index = m_entry_index, | ||
| 159 | .offsets{}, | ||
| 160 | .entry{}, | ||
| 161 | }; | ||
| 162 | std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets), | ||
| 163 | sizeof(BucketTree::Offsets)); | ||
| 164 | std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType)); | ||
| 165 | |||
| 166 | // Scan. | ||
| 167 | R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param)); | ||
| 168 | } | ||
| 169 | |||
| 170 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h new file mode 100644 index 000000000..5503613fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 7 | |||
| 8 | namespace FileSys::impl { | ||
| 9 | |||
| 10 | class SafeValue { | ||
| 11 | public: | ||
| 12 | static s64 GetInt64(const void* ptr) { | ||
| 13 | s64 value; | ||
| 14 | std::memcpy(std::addressof(value), ptr, sizeof(s64)); | ||
| 15 | return value; | ||
| 16 | } | ||
| 17 | |||
| 18 | static s64 GetInt64(const s64* ptr) { | ||
| 19 | return GetInt64(static_cast<const void*>(ptr)); | ||
| 20 | } | ||
| 21 | |||
| 22 | static s64 GetInt64(const s64& v) { | ||
| 23 | return GetInt64(std::addressof(v)); | ||
| 24 | } | ||
| 25 | |||
| 26 | static void SetInt64(void* dst, const void* src) { | ||
| 27 | std::memcpy(dst, src, sizeof(s64)); | ||
| 28 | } | ||
| 29 | |||
| 30 | static void SetInt64(void* dst, const s64* src) { | ||
| 31 | return SetInt64(dst, static_cast<const void*>(src)); | ||
| 32 | } | ||
| 33 | |||
| 34 | static void SetInt64(void* dst, const s64& v) { | ||
| 35 | return SetInt64(dst, std::addressof(v)); | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | |||
| 39 | template <typename IteratorType> | ||
| 40 | struct BucketTreeNode { | ||
| 41 | using Header = BucketTree::NodeHeader; | ||
| 42 | |||
| 43 | Header header; | ||
| 44 | |||
| 45 | s32 GetCount() const { | ||
| 46 | return this->header.count; | ||
| 47 | } | ||
| 48 | |||
| 49 | void* GetArray() { | ||
| 50 | return std::addressof(this->header) + 1; | ||
| 51 | } | ||
| 52 | template <typename T> | ||
| 53 | T* GetArray() { | ||
| 54 | return reinterpret_cast<T*>(this->GetArray()); | ||
| 55 | } | ||
| 56 | const void* GetArray() const { | ||
| 57 | return std::addressof(this->header) + 1; | ||
| 58 | } | ||
| 59 | template <typename T> | ||
| 60 | const T* GetArray() const { | ||
| 61 | return reinterpret_cast<const T*>(this->GetArray()); | ||
| 62 | } | ||
| 63 | |||
| 64 | s64 GetBeginOffset() const { | ||
| 65 | return *this->GetArray<s64>(); | ||
| 66 | } | ||
| 67 | s64 GetEndOffset() const { | ||
| 68 | return this->header.offset; | ||
| 69 | } | ||
| 70 | |||
| 71 | IteratorType GetBegin() { | ||
| 72 | return IteratorType(this->GetArray<s64>()); | ||
| 73 | } | ||
| 74 | IteratorType GetEnd() { | ||
| 75 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 76 | } | ||
| 77 | IteratorType GetBegin() const { | ||
| 78 | return IteratorType(this->GetArray<s64>()); | ||
| 79 | } | ||
| 80 | IteratorType GetEnd() const { | ||
| 81 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 82 | } | ||
| 83 | |||
| 84 | IteratorType GetBegin(size_t entry_size) { | ||
| 85 | return IteratorType(this->GetArray(), entry_size); | ||
| 86 | } | ||
| 87 | IteratorType GetEnd(size_t entry_size) { | ||
| 88 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 89 | } | ||
| 90 | IteratorType GetBegin(size_t entry_size) const { | ||
| 91 | return IteratorType(this->GetArray(), entry_size); | ||
| 92 | } | ||
| 93 | IteratorType GetEnd(size_t entry_size) const { | ||
| 94 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 95 | } | ||
| 96 | }; | ||
| 97 | |||
| 98 | constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, | ||
| 99 | s32 entry_index) { | ||
| 100 | return entry_set_offset + sizeof(BucketTree::NodeHeader) + | ||
| 101 | entry_index * static_cast<s64>(entry_size); | ||
| 102 | } | ||
| 103 | |||
| 104 | constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, | ||
| 105 | size_t entry_size, s32 entry_index) { | ||
| 106 | return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size, | ||
| 107 | entry_index); | ||
| 108 | } | ||
| 109 | |||
| 110 | } // namespace FileSys::impl | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h new file mode 100644 index 000000000..33d93938e --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h | |||
| @@ -0,0 +1,963 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/literals.h" | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | using namespace Common::Literals; | ||
| 18 | |||
| 19 | class CompressedStorage : public IReadOnlyStorage { | ||
| 20 | YUZU_NON_COPYABLE(CompressedStorage); | ||
| 21 | YUZU_NON_MOVEABLE(CompressedStorage); | ||
| 22 | |||
| 23 | public: | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | struct Entry { | ||
| 27 | s64 virt_offset; | ||
| 28 | s64 phys_offset; | ||
| 29 | CompressionType compression_type; | ||
| 30 | s32 phys_size; | ||
| 31 | |||
| 32 | s64 GetPhysicalSize() const { | ||
| 33 | return this->phys_size; | ||
| 34 | } | ||
| 35 | }; | ||
| 36 | static_assert(std::is_trivial_v<Entry>); | ||
| 37 | static_assert(sizeof(Entry) == 0x18); | ||
| 38 | |||
| 39 | public: | ||
| 40 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 41 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 42 | } | ||
| 43 | |||
| 44 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 45 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 46 | } | ||
| 47 | |||
| 48 | private: | ||
| 49 | class CompressedStorageCore { | ||
| 50 | YUZU_NON_COPYABLE(CompressedStorageCore); | ||
| 51 | YUZU_NON_MOVEABLE(CompressedStorageCore); | ||
| 52 | |||
| 53 | public: | ||
| 54 | CompressedStorageCore() : m_table(), m_data_storage() {} | ||
| 55 | |||
| 56 | ~CompressedStorageCore() { | ||
| 57 | this->Finalize(); | ||
| 58 | } | ||
| 59 | |||
| 60 | public: | ||
| 61 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, | ||
| 62 | VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max, | ||
| 63 | size_t continuous_reading_size_max, | ||
| 64 | GetDecompressorFunction get_decompressor) { | ||
| 65 | // Check pre-conditions. | ||
| 66 | ASSERT(0 < block_size_max); | ||
| 67 | ASSERT(block_size_max <= continuous_reading_size_max); | ||
| 68 | ASSERT(get_decompressor != nullptr); | ||
| 69 | |||
| 70 | // Initialize our entry table. | ||
| 71 | R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), | ||
| 72 | bktr_entry_count)); | ||
| 73 | |||
| 74 | // Set our other fields. | ||
| 75 | m_block_size_max = block_size_max; | ||
| 76 | m_continuous_reading_size_max = continuous_reading_size_max; | ||
| 77 | m_data_storage = data_storage; | ||
| 78 | m_get_decompressor_function = get_decompressor; | ||
| 79 | |||
| 80 | R_SUCCEED(); | ||
| 81 | } | ||
| 82 | |||
| 83 | void Finalize() { | ||
| 84 | if (this->IsInitialized()) { | ||
| 85 | m_table.Finalize(); | ||
| 86 | m_data_storage = VirtualFile(); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | VirtualFile GetDataStorage() { | ||
| 91 | return m_data_storage; | ||
| 92 | } | ||
| 93 | |||
| 94 | Result GetDataStorageSize(s64* out) { | ||
| 95 | // Check pre-conditions. | ||
| 96 | ASSERT(out != nullptr); | ||
| 97 | |||
| 98 | // Get size. | ||
| 99 | *out = m_data_storage->GetSize(); | ||
| 100 | |||
| 101 | R_SUCCEED(); | ||
| 102 | } | ||
| 103 | |||
| 104 | BucketTree& GetEntryTable() { | ||
| 105 | return m_table; | ||
| 106 | } | ||
| 107 | |||
| 108 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, | ||
| 109 | s64 offset, s64 size) { | ||
| 110 | // Check pre-conditions. | ||
| 111 | ASSERT(offset >= 0); | ||
| 112 | ASSERT(size >= 0); | ||
| 113 | ASSERT(this->IsInitialized()); | ||
| 114 | |||
| 115 | // Check that we can output the count. | ||
| 116 | R_UNLESS(out_read_count != nullptr, ResultNullptrArgument); | ||
| 117 | |||
| 118 | // Check that we have anything to read at all. | ||
| 119 | R_SUCCEED_IF(size == 0); | ||
| 120 | |||
| 121 | // Check that either we have a buffer, or this is to determine how many we need. | ||
| 122 | if (max_entry_count != 0) { | ||
| 123 | R_UNLESS(out_entries != nullptr, ResultNullptrArgument); | ||
| 124 | } | ||
| 125 | |||
| 126 | // Get the table offsets. | ||
| 127 | BucketTree::Offsets table_offsets; | ||
| 128 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 129 | |||
| 130 | // Validate arguments. | ||
| 131 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 132 | |||
| 133 | // Find the offset in our tree. | ||
| 134 | BucketTree::Visitor visitor; | ||
| 135 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 136 | { | ||
| 137 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 138 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 139 | ResultUnexpectedInCompressedStorageA); | ||
| 140 | } | ||
| 141 | |||
| 142 | // Get the entries. | ||
| 143 | const auto end_offset = offset + size; | ||
| 144 | s32 read_count = 0; | ||
| 145 | while (visitor.Get<Entry>()->virt_offset < end_offset) { | ||
| 146 | // If we should be setting the output, do so. | ||
| 147 | if (max_entry_count != 0) { | ||
| 148 | // Ensure we only read as many entries as we can. | ||
| 149 | if (read_count >= max_entry_count) { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | // Set the current output entry. | ||
| 154 | out_entries[read_count] = *visitor.Get<Entry>(); | ||
| 155 | } | ||
| 156 | |||
| 157 | // Increase the read count. | ||
| 158 | ++read_count; | ||
| 159 | |||
| 160 | // If we're at the end, we're done. | ||
| 161 | if (!visitor.CanMoveNext()) { | ||
| 162 | break; | ||
| 163 | } | ||
| 164 | |||
| 165 | // Move to the next entry. | ||
| 166 | R_TRY(visitor.MoveNext()); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Set the output read count. | ||
| 170 | *out_read_count = read_count; | ||
| 171 | R_SUCCEED(); | ||
| 172 | } | ||
| 173 | |||
| 174 | Result GetSize(s64* out) { | ||
| 175 | // Check pre-conditions. | ||
| 176 | ASSERT(out != nullptr); | ||
| 177 | |||
| 178 | // Get our table offsets. | ||
| 179 | BucketTree::Offsets offsets; | ||
| 180 | R_TRY(m_table.GetOffsets(std::addressof(offsets))); | ||
| 181 | |||
| 182 | // Set the output. | ||
| 183 | *out = offsets.end_offset; | ||
| 184 | R_SUCCEED(); | ||
| 185 | } | ||
| 186 | |||
| 187 | Result OperatePerEntry(s64 offset, s64 size, auto f) { | ||
| 188 | // Check pre-conditions. | ||
| 189 | ASSERT(offset >= 0); | ||
| 190 | ASSERT(size >= 0); | ||
| 191 | ASSERT(this->IsInitialized()); | ||
| 192 | |||
| 193 | // Succeed if there's nothing to operate on. | ||
| 194 | R_SUCCEED_IF(size == 0); | ||
| 195 | |||
| 196 | // Get the table offsets. | ||
| 197 | BucketTree::Offsets table_offsets; | ||
| 198 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 199 | |||
| 200 | // Validate arguments. | ||
| 201 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 202 | |||
| 203 | // Find the offset in our tree. | ||
| 204 | BucketTree::Visitor visitor; | ||
| 205 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 206 | { | ||
| 207 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 208 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 209 | ResultUnexpectedInCompressedStorageA); | ||
| 210 | } | ||
| 211 | |||
| 212 | // Prepare to operate in chunks. | ||
| 213 | auto cur_offset = offset; | ||
| 214 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 215 | |||
| 216 | while (cur_offset < end_offset) { | ||
| 217 | // Get the current entry. | ||
| 218 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 219 | |||
| 220 | // Get and validate the entry's offset. | ||
| 221 | const auto cur_entry_offset = cur_entry.virt_offset; | ||
| 222 | R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA); | ||
| 223 | |||
| 224 | // Get and validate the next entry offset. | ||
| 225 | s64 next_entry_offset; | ||
| 226 | if (visitor.CanMoveNext()) { | ||
| 227 | R_TRY(visitor.MoveNext()); | ||
| 228 | next_entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 229 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), | ||
| 230 | ResultUnexpectedInCompressedStorageA); | ||
| 231 | } else { | ||
| 232 | next_entry_offset = table_offsets.end_offset; | ||
| 233 | } | ||
| 234 | R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA); | ||
| 235 | |||
| 236 | // Get the offset of the entry in the data we read. | ||
| 237 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 238 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 239 | ASSERT(data_size > 0); | ||
| 240 | |||
| 241 | // Determine how much is left. | ||
| 242 | const auto remaining_size = end_offset - cur_offset; | ||
| 243 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 244 | ASSERT(cur_size <= size); | ||
| 245 | |||
| 246 | // Get the data storage size. | ||
| 247 | s64 storage_size = m_data_storage->GetSize(); | ||
| 248 | |||
| 249 | // Check that our read remains naively physically in bounds. | ||
| 250 | R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size, | ||
| 251 | ResultUnexpectedInCompressedStorageC); | ||
| 252 | |||
| 253 | // If we have any compression, verify that we remain physically in bounds. | ||
| 254 | if (cur_entry.compression_type != CompressionType::None) { | ||
| 255 | R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size, | ||
| 256 | ResultUnexpectedInCompressedStorageC); | ||
| 257 | } | ||
| 258 | |||
| 259 | // Check that block alignment requirements are met. | ||
| 260 | if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) { | ||
| 261 | R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment), | ||
| 262 | ResultUnexpectedInCompressedStorageA); | ||
| 263 | } | ||
| 264 | |||
| 265 | // Invoke the operator. | ||
| 266 | bool is_continuous = true; | ||
| 267 | R_TRY( | ||
| 268 | f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size)); | ||
| 269 | |||
| 270 | // If not continuous, we're done. | ||
| 271 | if (!is_continuous) { | ||
| 272 | break; | ||
| 273 | } | ||
| 274 | |||
| 275 | // Advance. | ||
| 276 | cur_offset += cur_size; | ||
| 277 | } | ||
| 278 | |||
| 279 | R_SUCCEED(); | ||
| 280 | } | ||
| 281 | |||
| 282 | public: | ||
| 283 | using ReadImplFunction = std::function<Result(void*, size_t)>; | ||
| 284 | using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>; | ||
| 285 | |||
| 286 | public: | ||
| 287 | Result Read(s64 offset, s64 size, const ReadFunction& read_func) { | ||
| 288 | // Check pre-conditions. | ||
| 289 | ASSERT(offset >= 0); | ||
| 290 | ASSERT(this->IsInitialized()); | ||
| 291 | |||
| 292 | // Succeed immediately, if we have nothing to read. | ||
| 293 | R_SUCCEED_IF(size == 0); | ||
| 294 | |||
| 295 | // Declare read lambda. | ||
| 296 | constexpr int EntriesCountMax = 0x80; | ||
| 297 | struct Entries { | ||
| 298 | CompressionType compression_type; | ||
| 299 | u32 gap_from_prev; | ||
| 300 | u32 physical_size; | ||
| 301 | u32 virtual_size; | ||
| 302 | }; | ||
| 303 | std::array<Entries, EntriesCountMax> entries; | ||
| 304 | s32 entry_count = 0; | ||
| 305 | Entry prev_entry = { | ||
| 306 | .virt_offset = -1, | ||
| 307 | .phys_offset{}, | ||
| 308 | .compression_type{}, | ||
| 309 | .phys_size{}, | ||
| 310 | }; | ||
| 311 | bool will_allocate_pooled_buffer = false; | ||
| 312 | s64 required_access_physical_offset = 0; | ||
| 313 | s64 required_access_physical_size = 0; | ||
| 314 | |||
| 315 | auto PerformRequiredRead = [&]() -> Result { | ||
| 316 | // If there are no entries, we have nothing to do. | ||
| 317 | R_SUCCEED_IF(entry_count == 0); | ||
| 318 | |||
| 319 | // Get the remaining size in a convenient form. | ||
| 320 | const size_t total_required_size = | ||
| 321 | static_cast<size_t>(required_access_physical_size); | ||
| 322 | |||
| 323 | // Perform the read based on whether we need to allocate a buffer. | ||
| 324 | if (will_allocate_pooled_buffer) { | ||
| 325 | // Allocate a pooled buffer. | ||
| 326 | PooledBuffer pooled_buffer; | ||
| 327 | if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) { | ||
| 328 | pooled_buffer.Allocate(total_required_size, m_block_size_max); | ||
| 329 | } else { | ||
| 330 | pooled_buffer.AllocateParticularlyLarge( | ||
| 331 | std::min<size_t>( | ||
| 332 | total_required_size, | ||
| 333 | PooledBuffer::GetAllocatableParticularlyLargeSizeMax()), | ||
| 334 | m_block_size_max); | ||
| 335 | } | ||
| 336 | |||
| 337 | // Read each of the entries. | ||
| 338 | for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) { | ||
| 339 | // Determine the current read size. | ||
| 340 | bool will_use_pooled_buffer = false; | ||
| 341 | const size_t cur_read_size = [&]() -> size_t { | ||
| 342 | if (const size_t target_entry_size = | ||
| 343 | static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 344 | static_cast<size_t>(entries[entry_idx].gap_from_prev); | ||
| 345 | target_entry_size <= pooled_buffer.GetSize()) { | ||
| 346 | // We'll be using the pooled buffer. | ||
| 347 | will_use_pooled_buffer = true; | ||
| 348 | |||
| 349 | // Determine how much we can read. | ||
| 350 | const size_t max_size = std::min<size_t>( | ||
| 351 | required_access_physical_size, pooled_buffer.GetSize()); | ||
| 352 | |||
| 353 | size_t read_size = 0; | ||
| 354 | for (auto n = entry_idx; n < entry_count; ++n) { | ||
| 355 | const size_t cur_entry_size = | ||
| 356 | static_cast<size_t>(entries[n].physical_size) + | ||
| 357 | static_cast<size_t>(entries[n].gap_from_prev); | ||
| 358 | if (read_size + cur_entry_size > max_size) { | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | |||
| 362 | read_size += cur_entry_size; | ||
| 363 | } | ||
| 364 | |||
| 365 | return read_size; | ||
| 366 | } else { | ||
| 367 | // If we don't fit, we must be uncompressed. | ||
| 368 | ASSERT(entries[entry_idx].compression_type == | ||
| 369 | CompressionType::None); | ||
| 370 | |||
| 371 | // We can perform the whole of an uncompressed read directly. | ||
| 372 | return entries[entry_idx].virtual_size; | ||
| 373 | } | ||
| 374 | }(); | ||
| 375 | |||
| 376 | // Perform the read based on whether or not we'll use the pooled buffer. | ||
| 377 | if (will_use_pooled_buffer) { | ||
| 378 | // Read the compressed data into the pooled buffer. | ||
| 379 | auto* const buffer = pooled_buffer.GetBuffer(); | ||
| 380 | m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size, | ||
| 381 | required_access_physical_offset); | ||
| 382 | |||
| 383 | // Decompress the data. | ||
| 384 | size_t buffer_offset; | ||
| 385 | for (buffer_offset = 0; | ||
| 386 | entry_idx < entry_count && | ||
| 387 | ((static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 388 | static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 || | ||
| 389 | buffer_offset < cur_read_size); | ||
| 390 | buffer_offset += entries[entry_idx++].physical_size) { | ||
| 391 | // Advance by the relevant gap. | ||
| 392 | buffer_offset += entries[entry_idx].gap_from_prev; | ||
| 393 | |||
| 394 | const auto compression_type = entries[entry_idx].compression_type; | ||
| 395 | switch (compression_type) { | ||
| 396 | case CompressionType::None: { | ||
| 397 | // Check that we can remain within bounds. | ||
| 398 | ASSERT(buffer_offset + entries[entry_idx].virtual_size <= | ||
| 399 | cur_read_size); | ||
| 400 | |||
| 401 | // Perform no decompression. | ||
| 402 | R_TRY(read_func( | ||
| 403 | entries[entry_idx].virtual_size, | ||
| 404 | [&](void* dst, size_t dst_size) -> Result { | ||
| 405 | // Check that the size is valid. | ||
| 406 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 407 | |||
| 408 | // We have no compression, so just copy the data | ||
| 409 | // out. | ||
| 410 | std::memcpy(dst, buffer + buffer_offset, | ||
| 411 | entries[entry_idx].virtual_size); | ||
| 412 | R_SUCCEED(); | ||
| 413 | })); | ||
| 414 | |||
| 415 | break; | ||
| 416 | } | ||
| 417 | case CompressionType::Zeros: { | ||
| 418 | // Check that we can remain within bounds. | ||
| 419 | ASSERT(buffer_offset <= cur_read_size); | ||
| 420 | |||
| 421 | // Zero the memory. | ||
| 422 | R_TRY(read_func( | ||
| 423 | entries[entry_idx].virtual_size, | ||
| 424 | [&](void* dst, size_t dst_size) -> Result { | ||
| 425 | // Check that the size is valid. | ||
| 426 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 427 | |||
| 428 | // The data is zeroes, so zero the buffer. | ||
| 429 | std::memset(dst, 0, entries[entry_idx].virtual_size); | ||
| 430 | R_SUCCEED(); | ||
| 431 | })); | ||
| 432 | |||
| 433 | break; | ||
| 434 | } | ||
| 435 | default: { | ||
| 436 | // Check that we can remain within bounds. | ||
| 437 | ASSERT(buffer_offset + entries[entry_idx].physical_size <= | ||
| 438 | cur_read_size); | ||
| 439 | |||
| 440 | // Get the decompressor. | ||
| 441 | const auto decompressor = | ||
| 442 | this->GetDecompressor(compression_type); | ||
| 443 | R_UNLESS(decompressor != nullptr, | ||
| 444 | ResultUnexpectedInCompressedStorageB); | ||
| 445 | |||
| 446 | // Decompress the data. | ||
| 447 | R_TRY(read_func(entries[entry_idx].virtual_size, | ||
| 448 | [&](void* dst, size_t dst_size) -> Result { | ||
| 449 | // Check that the size is valid. | ||
| 450 | ASSERT(dst_size == | ||
| 451 | entries[entry_idx].virtual_size); | ||
| 452 | |||
| 453 | // Perform the decompression. | ||
| 454 | R_RETURN(decompressor( | ||
| 455 | dst, entries[entry_idx].virtual_size, | ||
| 456 | buffer + buffer_offset, | ||
| 457 | entries[entry_idx].physical_size)); | ||
| 458 | })); | ||
| 459 | |||
| 460 | break; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | // Check that we processed the correct amount of data. | ||
| 466 | ASSERT(buffer_offset == cur_read_size); | ||
| 467 | } else { | ||
| 468 | // Account for the gap from the previous entry. | ||
| 469 | required_access_physical_offset += entries[entry_idx].gap_from_prev; | ||
| 470 | required_access_physical_size -= entries[entry_idx].gap_from_prev; | ||
| 471 | |||
| 472 | // We don't need the buffer (as the data is uncompressed), so just | ||
| 473 | // execute the read. | ||
| 474 | R_TRY( | ||
| 475 | read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 476 | // Check that the size is valid. | ||
| 477 | ASSERT(dst_size == cur_read_size); | ||
| 478 | |||
| 479 | // Perform the read. | ||
| 480 | m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size, | ||
| 481 | required_access_physical_offset); | ||
| 482 | |||
| 483 | R_SUCCEED(); | ||
| 484 | })); | ||
| 485 | } | ||
| 486 | |||
| 487 | // Advance on. | ||
| 488 | required_access_physical_offset += cur_read_size; | ||
| 489 | required_access_physical_size -= cur_read_size; | ||
| 490 | } | ||
| 491 | |||
| 492 | // Verify that we have nothing remaining to read. | ||
| 493 | ASSERT(required_access_physical_size == 0); | ||
| 494 | |||
| 495 | R_SUCCEED(); | ||
| 496 | } else { | ||
| 497 | // We don't need a buffer, so just execute the read. | ||
| 498 | R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 499 | // Check that the size is valid. | ||
| 500 | ASSERT(dst_size == total_required_size); | ||
| 501 | |||
| 502 | // Perform the read. | ||
| 503 | m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size, | ||
| 504 | required_access_physical_offset); | ||
| 505 | |||
| 506 | R_SUCCEED(); | ||
| 507 | })); | ||
| 508 | } | ||
| 509 | |||
| 510 | R_SUCCEED(); | ||
| 511 | }; | ||
| 512 | |||
| 513 | R_TRY(this->OperatePerEntry( | ||
| 514 | offset, size, | ||
| 515 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 516 | s64 data_offset, s64 read_size) -> Result { | ||
| 517 | // Determine the physical extents. | ||
| 518 | s64 physical_offset, physical_size; | ||
| 519 | if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { | ||
| 520 | physical_offset = entry.phys_offset + data_offset; | ||
| 521 | physical_size = read_size; | ||
| 522 | } else { | ||
| 523 | physical_offset = entry.phys_offset; | ||
| 524 | physical_size = entry.GetPhysicalSize(); | ||
| 525 | } | ||
| 526 | |||
| 527 | // If we have a pending data storage operation, perform it if we have to. | ||
| 528 | const s64 required_access_physical_end = | ||
| 529 | required_access_physical_offset + required_access_physical_size; | ||
| 530 | if (required_access_physical_size > 0) { | ||
| 531 | const bool required_by_gap = | ||
| 532 | !(required_access_physical_end <= physical_offset && | ||
| 533 | physical_offset <= Common::AlignUp(required_access_physical_end, | ||
| 534 | CompressionBlockAlignment)); | ||
| 535 | const bool required_by_continuous_size = | ||
| 536 | ((physical_size + physical_offset) - required_access_physical_end) + | ||
| 537 | required_access_physical_size > | ||
| 538 | static_cast<s64>(m_continuous_reading_size_max); | ||
| 539 | const bool required_by_entry_count = entry_count == EntriesCountMax; | ||
| 540 | if (required_by_gap || required_by_continuous_size || | ||
| 541 | required_by_entry_count) { | ||
| 542 | // Check that our planned access is sane. | ||
| 543 | ASSERT(!will_allocate_pooled_buffer || | ||
| 544 | required_access_physical_size <= | ||
| 545 | static_cast<s64>(m_continuous_reading_size_max)); | ||
| 546 | |||
| 547 | // Perform the required read. | ||
| 548 | const Result rc = PerformRequiredRead(); | ||
| 549 | if (R_FAILED(rc)) { | ||
| 550 | R_THROW(rc); | ||
| 551 | } | ||
| 552 | |||
| 553 | // Reset our requirements. | ||
| 554 | prev_entry.virt_offset = -1; | ||
| 555 | required_access_physical_size = 0; | ||
| 556 | entry_count = 0; | ||
| 557 | will_allocate_pooled_buffer = false; | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | // Sanity check that we're within bounds on entries. | ||
| 562 | ASSERT(entry_count < EntriesCountMax); | ||
| 563 | |||
| 564 | // Determine if a buffer allocation is needed. | ||
| 565 | if (entry.compression_type != CompressionType::None || | ||
| 566 | (prev_entry.virt_offset >= 0 && | ||
| 567 | entry.virt_offset - prev_entry.virt_offset != | ||
| 568 | entry.phys_offset - prev_entry.phys_offset)) { | ||
| 569 | will_allocate_pooled_buffer = true; | ||
| 570 | } | ||
| 571 | |||
| 572 | // If we need to access the data storage, update our required access parameters. | ||
| 573 | if (CompressionTypeUtility::IsDataStorageAccessRequired( | ||
| 574 | entry.compression_type)) { | ||
| 575 | // If the data is compressed, ensure the access is sane. | ||
| 576 | if (entry.compression_type != CompressionType::None) { | ||
| 577 | R_UNLESS(data_offset == 0, ResultInvalidOffset); | ||
| 578 | R_UNLESS(virtual_data_size == read_size, ResultInvalidSize); | ||
| 579 | R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max), | ||
| 580 | ResultUnexpectedInCompressedStorageD); | ||
| 581 | } | ||
| 582 | |||
| 583 | // Update the required access parameters. | ||
| 584 | s64 gap_from_prev; | ||
| 585 | if (required_access_physical_size > 0) { | ||
| 586 | gap_from_prev = physical_offset - required_access_physical_end; | ||
| 587 | } else { | ||
| 588 | gap_from_prev = 0; | ||
| 589 | required_access_physical_offset = physical_offset; | ||
| 590 | } | ||
| 591 | required_access_physical_size += physical_size + gap_from_prev; | ||
| 592 | |||
| 593 | // Create an entry to access the data storage. | ||
| 594 | entries[entry_count++] = { | ||
| 595 | .compression_type = entry.compression_type, | ||
| 596 | .gap_from_prev = static_cast<u32>(gap_from_prev), | ||
| 597 | .physical_size = static_cast<u32>(physical_size), | ||
| 598 | .virtual_size = static_cast<u32>(read_size), | ||
| 599 | }; | ||
| 600 | } else { | ||
| 601 | // Verify that we're allowed to be operating on the non-data-storage-access | ||
| 602 | // type. | ||
| 603 | R_UNLESS(entry.compression_type == CompressionType::Zeros, | ||
| 604 | ResultUnexpectedInCompressedStorageB); | ||
| 605 | |||
| 606 | // If we have entries, create a fake entry for the zero region. | ||
| 607 | if (entry_count != 0) { | ||
| 608 | // We need to have a physical size. | ||
| 609 | R_UNLESS(entry.GetPhysicalSize() != 0, | ||
| 610 | ResultUnexpectedInCompressedStorageD); | ||
| 611 | |||
| 612 | // Create a fake entry. | ||
| 613 | entries[entry_count++] = { | ||
| 614 | .compression_type = CompressionType::Zeros, | ||
| 615 | .gap_from_prev = 0, | ||
| 616 | .physical_size = 0, | ||
| 617 | .virtual_size = static_cast<u32>(read_size), | ||
| 618 | }; | ||
| 619 | } else { | ||
| 620 | // We have no entries, so we can just perform the read. | ||
| 621 | const Result rc = | ||
| 622 | read_func(static_cast<size_t>(read_size), | ||
| 623 | [&](void* dst, size_t dst_size) -> Result { | ||
| 624 | // Check the space we should zero is correct. | ||
| 625 | ASSERT(dst_size == static_cast<size_t>(read_size)); | ||
| 626 | |||
| 627 | // Zero the memory. | ||
| 628 | std::memset(dst, 0, read_size); | ||
| 629 | R_SUCCEED(); | ||
| 630 | }); | ||
| 631 | if (R_FAILED(rc)) { | ||
| 632 | R_THROW(rc); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | // Set the previous entry. | ||
| 638 | prev_entry = entry; | ||
| 639 | |||
| 640 | // We're continuous. | ||
| 641 | *out_continuous = true; | ||
| 642 | R_SUCCEED(); | ||
| 643 | })); | ||
| 644 | |||
| 645 | // If we still have a pending access, perform it. | ||
| 646 | if (required_access_physical_size != 0) { | ||
| 647 | R_TRY(PerformRequiredRead()); | ||
| 648 | } | ||
| 649 | |||
| 650 | R_SUCCEED(); | ||
| 651 | } | ||
| 652 | |||
| 653 | private: | ||
| 654 | DecompressorFunction GetDecompressor(CompressionType type) const { | ||
| 655 | // Check that we can get a decompressor for the type. | ||
| 656 | if (CompressionTypeUtility::IsUnknownType(type)) { | ||
| 657 | return nullptr; | ||
| 658 | } | ||
| 659 | |||
| 660 | // Get the decompressor. | ||
| 661 | return m_get_decompressor_function(type); | ||
| 662 | } | ||
| 663 | |||
| 664 | bool IsInitialized() const { | ||
| 665 | return m_table.IsInitialized(); | ||
| 666 | } | ||
| 667 | |||
| 668 | private: | ||
| 669 | size_t m_block_size_max; | ||
| 670 | size_t m_continuous_reading_size_max; | ||
| 671 | BucketTree m_table; | ||
| 672 | VirtualFile m_data_storage; | ||
| 673 | GetDecompressorFunction m_get_decompressor_function; | ||
| 674 | }; | ||
| 675 | |||
| 676 | class CacheManager { | ||
| 677 | YUZU_NON_COPYABLE(CacheManager); | ||
| 678 | YUZU_NON_MOVEABLE(CacheManager); | ||
| 679 | |||
| 680 | private: | ||
| 681 | struct AccessRange { | ||
| 682 | s64 virtual_offset; | ||
| 683 | s64 virtual_size; | ||
| 684 | u32 physical_size; | ||
| 685 | bool is_block_alignment_required; | ||
| 686 | |||
| 687 | s64 GetEndVirtualOffset() const { | ||
| 688 | return this->virtual_offset + this->virtual_size; | ||
| 689 | } | ||
| 690 | }; | ||
| 691 | static_assert(std::is_trivial_v<AccessRange>); | ||
| 692 | |||
| 693 | public: | ||
| 694 | CacheManager() = default; | ||
| 695 | |||
| 696 | public: | ||
| 697 | Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1, | ||
| 698 | size_t max_cache_entries) { | ||
| 699 | // Set our fields. | ||
| 700 | m_storage_size = storage_size; | ||
| 701 | |||
| 702 | R_SUCCEED(); | ||
| 703 | } | ||
| 704 | |||
| 705 | Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) { | ||
| 706 | // If we have nothing to read, succeed. | ||
| 707 | R_SUCCEED_IF(size == 0); | ||
| 708 | |||
| 709 | // Check that we have a buffer to read into. | ||
| 710 | R_UNLESS(buffer != nullptr, ResultNullptrArgument); | ||
| 711 | |||
| 712 | // Check that the read is in bounds. | ||
| 713 | R_UNLESS(offset <= m_storage_size, ResultInvalidOffset); | ||
| 714 | |||
| 715 | // Determine how much we can read. | ||
| 716 | const size_t read_size = std::min<size_t>(size, m_storage_size - offset); | ||
| 717 | |||
| 718 | // Create head/tail ranges. | ||
| 719 | AccessRange head_range = {}; | ||
| 720 | AccessRange tail_range = {}; | ||
| 721 | bool is_tail_set = false; | ||
| 722 | |||
| 723 | // Operate to determine the head range. | ||
| 724 | R_TRY(core.OperatePerEntry( | ||
| 725 | offset, 1, | ||
| 726 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 727 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 728 | // Set the head range. | ||
| 729 | head_range = { | ||
| 730 | .virtual_offset = entry.virt_offset, | ||
| 731 | .virtual_size = virtual_data_size, | ||
| 732 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 733 | .is_block_alignment_required = | ||
| 734 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 735 | entry.compression_type), | ||
| 736 | }; | ||
| 737 | |||
| 738 | // If required, set the tail range. | ||
| 739 | if (static_cast<s64>(offset + read_size) <= | ||
| 740 | entry.virt_offset + virtual_data_size) { | ||
| 741 | tail_range = { | ||
| 742 | .virtual_offset = entry.virt_offset, | ||
| 743 | .virtual_size = virtual_data_size, | ||
| 744 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 745 | .is_block_alignment_required = | ||
| 746 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 747 | entry.compression_type), | ||
| 748 | }; | ||
| 749 | is_tail_set = true; | ||
| 750 | } | ||
| 751 | |||
| 752 | // We only want to determine the head range, so we're not continuous. | ||
| 753 | *out_continuous = false; | ||
| 754 | R_SUCCEED(); | ||
| 755 | })); | ||
| 756 | |||
| 757 | // If necessary, determine the tail range. | ||
| 758 | if (!is_tail_set) { | ||
| 759 | R_TRY(core.OperatePerEntry( | ||
| 760 | offset + read_size - 1, 1, | ||
| 761 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 762 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 763 | // Set the tail range. | ||
| 764 | tail_range = { | ||
| 765 | .virtual_offset = entry.virt_offset, | ||
| 766 | .virtual_size = virtual_data_size, | ||
| 767 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 768 | .is_block_alignment_required = | ||
| 769 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 770 | entry.compression_type), | ||
| 771 | }; | ||
| 772 | |||
| 773 | // We only want to determine the tail range, so we're not continuous. | ||
| 774 | *out_continuous = false; | ||
| 775 | R_SUCCEED(); | ||
| 776 | })); | ||
| 777 | } | ||
| 778 | |||
| 779 | // Begin performing the accesses. | ||
| 780 | s64 cur_offset = offset; | ||
| 781 | size_t cur_size = read_size; | ||
| 782 | char* cur_dst = static_cast<char*>(buffer); | ||
| 783 | |||
| 784 | // Determine our alignment. | ||
| 785 | const bool head_unaligned = head_range.is_block_alignment_required && | ||
| 786 | (cur_offset != head_range.virtual_offset || | ||
| 787 | static_cast<s64>(cur_size) < head_range.virtual_size); | ||
| 788 | const bool tail_unaligned = [&]() -> bool { | ||
| 789 | if (tail_range.is_block_alignment_required) { | ||
| 790 | if (static_cast<s64>(cur_size + cur_offset) == | ||
| 791 | tail_range.GetEndVirtualOffset()) { | ||
| 792 | return false; | ||
| 793 | } else if (!head_unaligned) { | ||
| 794 | return true; | ||
| 795 | } else { | ||
| 796 | return head_range.GetEndVirtualOffset() < | ||
| 797 | static_cast<s64>(cur_size + cur_offset); | ||
| 798 | } | ||
| 799 | } else { | ||
| 800 | return false; | ||
| 801 | } | ||
| 802 | }(); | ||
| 803 | |||
| 804 | // Determine start/end offsets. | ||
| 805 | const s64 start_offset = | ||
| 806 | head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset; | ||
| 807 | const s64 end_offset = tail_range.is_block_alignment_required | ||
| 808 | ? tail_range.GetEndVirtualOffset() | ||
| 809 | : cur_offset + cur_size; | ||
| 810 | |||
| 811 | // Perform the read. | ||
| 812 | bool is_burst_reading = false; | ||
| 813 | R_TRY(core.Read( | ||
| 814 | start_offset, end_offset - start_offset, | ||
| 815 | [&](size_t size_buffer_required, | ||
| 816 | const CompressedStorageCore::ReadImplFunction& read_impl) -> Result { | ||
| 817 | // Determine whether we're burst reading. | ||
| 818 | const AccessRange* unaligned_range = nullptr; | ||
| 819 | if (!is_burst_reading) { | ||
| 820 | // Check whether we're using head, tail, or none as unaligned. | ||
| 821 | if (head_unaligned && head_range.virtual_offset <= cur_offset && | ||
| 822 | cur_offset < head_range.GetEndVirtualOffset()) { | ||
| 823 | unaligned_range = std::addressof(head_range); | ||
| 824 | } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset && | ||
| 825 | cur_offset < tail_range.GetEndVirtualOffset()) { | ||
| 826 | unaligned_range = std::addressof(tail_range); | ||
| 827 | } else { | ||
| 828 | is_burst_reading = true; | ||
| 829 | } | ||
| 830 | } | ||
| 831 | ASSERT((is_burst_reading ^ (unaligned_range != nullptr))); | ||
| 832 | |||
| 833 | // Perform reading by burst, or not. | ||
| 834 | if (is_burst_reading) { | ||
| 835 | // Check that the access is valid for burst reading. | ||
| 836 | ASSERT(size_buffer_required <= cur_size); | ||
| 837 | |||
| 838 | // Perform the read. | ||
| 839 | Result rc = read_impl(cur_dst, size_buffer_required); | ||
| 840 | if (R_FAILED(rc)) { | ||
| 841 | R_THROW(rc); | ||
| 842 | } | ||
| 843 | |||
| 844 | // Advance. | ||
| 845 | cur_dst += size_buffer_required; | ||
| 846 | cur_offset += size_buffer_required; | ||
| 847 | cur_size -= size_buffer_required; | ||
| 848 | |||
| 849 | // Determine whether we're going to continue burst reading. | ||
| 850 | const s64 offset_aligned = | ||
| 851 | tail_unaligned ? tail_range.virtual_offset : end_offset; | ||
| 852 | ASSERT(cur_offset <= offset_aligned); | ||
| 853 | |||
| 854 | if (offset_aligned <= cur_offset) { | ||
| 855 | is_burst_reading = false; | ||
| 856 | } | ||
| 857 | } else { | ||
| 858 | // We're not burst reading, so we have some unaligned range. | ||
| 859 | ASSERT(unaligned_range != nullptr); | ||
| 860 | |||
| 861 | // Check that the size is correct. | ||
| 862 | ASSERT(size_buffer_required == | ||
| 863 | static_cast<size_t>(unaligned_range->virtual_size)); | ||
| 864 | |||
| 865 | // Get a pooled buffer for our read. | ||
| 866 | PooledBuffer pooled_buffer; | ||
| 867 | pooled_buffer.Allocate(size_buffer_required, size_buffer_required); | ||
| 868 | |||
| 869 | // Perform read. | ||
| 870 | Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required); | ||
| 871 | if (R_FAILED(rc)) { | ||
| 872 | R_THROW(rc); | ||
| 873 | } | ||
| 874 | |||
| 875 | // Copy the data we read to the destination. | ||
| 876 | const size_t skip_size = cur_offset - unaligned_range->virtual_offset; | ||
| 877 | const size_t copy_size = std::min<size_t>( | ||
| 878 | cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset); | ||
| 879 | |||
| 880 | std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size); | ||
| 881 | |||
| 882 | // Advance. | ||
| 883 | cur_dst += copy_size; | ||
| 884 | cur_offset += copy_size; | ||
| 885 | cur_size -= copy_size; | ||
| 886 | } | ||
| 887 | |||
| 888 | R_SUCCEED(); | ||
| 889 | })); | ||
| 890 | |||
| 891 | R_SUCCEED(); | ||
| 892 | } | ||
| 893 | |||
| 894 | private: | ||
| 895 | s64 m_storage_size = 0; | ||
| 896 | }; | ||
| 897 | |||
| 898 | public: | ||
| 899 | CompressedStorage() = default; | ||
| 900 | virtual ~CompressedStorage() { | ||
| 901 | this->Finalize(); | ||
| 902 | } | ||
| 903 | |||
| 904 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 905 | s32 bktr_entry_count, size_t block_size_max, | ||
| 906 | size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor, | ||
| 907 | size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) { | ||
| 908 | // Initialize our core. | ||
| 909 | R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count, | ||
| 910 | block_size_max, continuous_reading_size_max, get_decompressor)); | ||
| 911 | |||
| 912 | // Get our core size. | ||
| 913 | s64 core_size = 0; | ||
| 914 | R_TRY(m_core.GetSize(std::addressof(core_size))); | ||
| 915 | |||
| 916 | // Initialize our cache manager. | ||
| 917 | R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries)); | ||
| 918 | |||
| 919 | R_SUCCEED(); | ||
| 920 | } | ||
| 921 | |||
| 922 | void Finalize() { | ||
| 923 | m_core.Finalize(); | ||
| 924 | } | ||
| 925 | |||
| 926 | VirtualFile GetDataStorage() { | ||
| 927 | return m_core.GetDataStorage(); | ||
| 928 | } | ||
| 929 | |||
| 930 | Result GetDataStorageSize(s64* out) { | ||
| 931 | R_RETURN(m_core.GetDataStorageSize(out)); | ||
| 932 | } | ||
| 933 | |||
| 934 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset, | ||
| 935 | s64 size) { | ||
| 936 | R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size)); | ||
| 937 | } | ||
| 938 | |||
| 939 | BucketTree& GetEntryTable() { | ||
| 940 | return m_core.GetEntryTable(); | ||
| 941 | } | ||
| 942 | |||
| 943 | public: | ||
| 944 | virtual size_t GetSize() const override { | ||
| 945 | s64 ret{}; | ||
| 946 | m_core.GetSize(&ret); | ||
| 947 | return ret; | ||
| 948 | } | ||
| 949 | |||
| 950 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 951 | if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) { | ||
| 952 | return size; | ||
| 953 | } else { | ||
| 954 | return 0; | ||
| 955 | } | ||
| 956 | } | ||
| 957 | |||
| 958 | private: | ||
| 959 | mutable CompressedStorageCore m_core; | ||
| 960 | mutable CacheManager m_cache_manager; | ||
| 961 | }; | ||
| 962 | |||
| 963 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h new file mode 100644 index 000000000..266e0a7e5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_common.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | enum class CompressionType : u8 { | ||
| 11 | None = 0, | ||
| 12 | Zeros = 1, | ||
| 13 | Two = 2, | ||
| 14 | Lz4 = 3, | ||
| 15 | Unknown = 4, | ||
| 16 | }; | ||
| 17 | |||
| 18 | using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t); | ||
| 19 | using GetDecompressorFunction = DecompressorFunction (*)(CompressionType); | ||
| 20 | |||
| 21 | constexpr s64 CompressionBlockAlignment = 0x10; | ||
| 22 | |||
| 23 | namespace CompressionTypeUtility { | ||
| 24 | |||
| 25 | constexpr bool IsBlockAlignmentRequired(CompressionType type) { | ||
| 26 | return type != CompressionType::None && type != CompressionType::Zeros; | ||
| 27 | } | ||
| 28 | |||
| 29 | constexpr bool IsDataStorageAccessRequired(CompressionType type) { | ||
| 30 | return type != CompressionType::Zeros; | ||
| 31 | } | ||
| 32 | |||
| 33 | constexpr bool IsRandomAccessible(CompressionType type) { | ||
| 34 | return type == CompressionType::None; | ||
| 35 | } | ||
| 36 | |||
| 37 | constexpr bool IsUnknownType(CompressionType type) { | ||
| 38 | return type >= CompressionType::Unknown; | ||
| 39 | } | ||
| 40 | |||
| 41 | } // namespace CompressionTypeUtility | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp new file mode 100644 index 000000000..ef552cefe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/lz4_compression.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 12 | auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size); | ||
| 13 | R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC); | ||
| 14 | R_SUCCEED(); | ||
| 15 | } | ||
| 16 | |||
| 17 | constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) { | ||
| 18 | switch (type) { | ||
| 19 | case CompressionType::Lz4: | ||
| 20 | return DecompressLz4; | ||
| 21 | default: | ||
| 22 | return nullptr; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | const NcaCompressionConfiguration& GetNcaCompressionConfiguration() { | ||
| 29 | static const NcaCompressionConfiguration configuration = { | ||
| 30 | .get_decompressor = GetNcaDecompressorFunction, | ||
| 31 | }; | ||
| 32 | |||
| 33 | return configuration; | ||
| 34 | } | ||
| 35 | |||
| 36 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h new file mode 100644 index 000000000..ec9b48e9a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCompressionConfiguration& GetNcaCompressionConfiguration(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 000000000..a4f0cde28 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/crypto/aes_util.h" | ||
| 5 | #include "core/crypto/key_manager.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size, | ||
| 13 | s32 key_type) { | ||
| 14 | if (key_type == static_cast<s32>(KeyType::ZeroKey)) { | ||
| 15 | std::memset(dst_key, 0, dst_key_size); | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | if (key_type == static_cast<s32>(KeyType::InvalidKey) || | ||
| 20 | key_type < static_cast<s32>(KeyType::ZeroKey) || | ||
| 21 | key_type >= static_cast<s32>(KeyType::NcaExternalKey)) { | ||
| 22 | std::memset(dst_key, 0xFF, dst_key_size); | ||
| 23 | return; | ||
| 24 | } | ||
| 25 | |||
| 26 | const auto& instance = Core::Crypto::KeyManager::Instance(); | ||
| 27 | |||
| 28 | if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || | ||
| 29 | key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) { | ||
| 30 | const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type; | ||
| 31 | const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header); | ||
| 32 | std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2)); | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | const s32 key_generation = | ||
| 37 | std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1; | ||
| 38 | const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount; | ||
| 39 | |||
| 40 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 41 | instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index), | ||
| 42 | Core::Crypto::Mode::ECB); | ||
| 43 | cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size, | ||
| 44 | reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt); | ||
| 45 | } | ||
| 46 | |||
| 47 | } // namespace | ||
| 48 | |||
| 49 | const NcaCryptoConfiguration& GetCryptoConfiguration() { | ||
| 50 | static const NcaCryptoConfiguration configuration = { | ||
| 51 | .header_1_sign_key_moduli{}, | ||
| 52 | .header_1_sign_key_public_exponent{}, | ||
| 53 | .key_area_encryption_key_source{}, | ||
| 54 | .header_encryption_key_source{}, | ||
| 55 | .header_encrypted_encryption_keys{}, | ||
| 56 | .generate_key = GenerateKey, | ||
| 57 | .verify_sign1{}, | ||
| 58 | .is_plaintext_header_available{}, | ||
| 59 | .is_available_sw_key{}, | ||
| 60 | }; | ||
| 61 | |||
| 62 | return configuration; | ||
| 63 | } | ||
| 64 | |||
| 65 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h new file mode 100644 index 000000000..7fd9c5a8d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCryptoConfiguration& GetCryptoConfiguration(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 000000000..4a75b5308 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 5 | #include "core/file_sys/vfs_offset.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage() | ||
| 10 | : m_data_size(-1) { | ||
| 11 | for (size_t i = 0; i < MaxLayers - 1; i++) { | ||
| 12 | m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>(); | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | Result HierarchicalIntegrityVerificationStorage::Initialize( | ||
| 17 | const HierarchicalIntegrityVerificationInformation& info, | ||
| 18 | HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries, | ||
| 19 | s8 buffer_level) { | ||
| 20 | // Validate preconditions. | ||
| 21 | ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); | ||
| 22 | |||
| 23 | // Set member variables. | ||
| 24 | m_max_layers = info.max_layers; | ||
| 25 | |||
| 26 | // Initialize the top level verification storage. | ||
| 27 | m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage], | ||
| 28 | storage[HierarchicalStorageInformation::Layer1Storage], | ||
| 29 | static_cast<s64>(1) << info.info[0].block_order, HashSize, | ||
| 30 | false); | ||
| 31 | |||
| 32 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 33 | ON_RESULT_FAILURE { | ||
| 34 | m_verify_storages[0]->Finalize(); | ||
| 35 | m_data_size = -1; | ||
| 36 | }; | ||
| 37 | |||
| 38 | // Initialize the top level buffer storage. | ||
| 39 | m_buffer_storages[0] = m_verify_storages[0]; | ||
| 40 | R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 41 | |||
| 42 | // Prepare to initialize the level storages. | ||
| 43 | s32 level = 0; | ||
| 44 | |||
| 45 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 46 | ON_RESULT_FAILURE_2 { | ||
| 47 | m_verify_storages[level + 1]->Finalize(); | ||
| 48 | for (; level > 0; --level) { | ||
| 49 | m_buffer_storages[level].reset(); | ||
| 50 | m_verify_storages[level]->Finalize(); | ||
| 51 | } | ||
| 52 | }; | ||
| 53 | |||
| 54 | // Initialize the level storages. | ||
| 55 | for (; level < m_max_layers - 3; ++level) { | ||
| 56 | // Initialize the verification storage. | ||
| 57 | auto buffer_storage = | ||
| 58 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 59 | m_verify_storages[level + 1]->Initialize( | ||
| 60 | std::move(buffer_storage), storage[level + 2], | ||
| 61 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 62 | static_cast<s64>(1) << info.info[level].block_order, false); | ||
| 63 | |||
| 64 | // Initialize the buffer storage. | ||
| 65 | m_buffer_storages[level + 1] = m_verify_storages[level + 1]; | ||
| 66 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 67 | ResultAllocationMemoryFailedAllocateShared); | ||
| 68 | } | ||
| 69 | |||
| 70 | // Initialize the final level storage. | ||
| 71 | { | ||
| 72 | // Initialize the verification storage. | ||
| 73 | auto buffer_storage = | ||
| 74 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 75 | m_verify_storages[level + 1]->Initialize( | ||
| 76 | std::move(buffer_storage), storage[level + 2], | ||
| 77 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 78 | static_cast<s64>(1) << info.info[level].block_order, true); | ||
| 79 | |||
| 80 | // Initialize the buffer storage. | ||
| 81 | m_buffer_storages[level + 1] = m_verify_storages[level + 1]; | ||
| 82 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 83 | ResultAllocationMemoryFailedAllocateShared); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Set the data size. | ||
| 87 | m_data_size = info.info[level + 1].size; | ||
| 88 | |||
| 89 | // We succeeded. | ||
| 90 | R_SUCCEED(); | ||
| 91 | } | ||
| 92 | |||
| 93 | void HierarchicalIntegrityVerificationStorage::Finalize() { | ||
| 94 | if (m_data_size >= 0) { | ||
| 95 | m_data_size = 0; | ||
| 96 | |||
| 97 | for (s32 level = m_max_layers - 2; level >= 0; --level) { | ||
| 98 | m_buffer_storages[level].reset(); | ||
| 99 | m_verify_storages[level]->Finalize(); | ||
| 100 | } | ||
| 101 | |||
| 102 | m_data_size = -1; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size, | ||
| 107 | size_t offset) const { | ||
| 108 | // Validate preconditions. | ||
| 109 | ASSERT(m_data_size >= 0); | ||
| 110 | |||
| 111 | // Succeed if zero-size. | ||
| 112 | if (size == 0) { | ||
| 113 | return size; | ||
| 114 | } | ||
| 115 | |||
| 116 | // Validate arguments. | ||
| 117 | ASSERT(buffer != nullptr); | ||
| 118 | |||
| 119 | // Read the data. | ||
| 120 | return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset); | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t HierarchicalIntegrityVerificationStorage::GetSize() const { | ||
| 124 | return m_data_size; | ||
| 125 | } | ||
| 126 | |||
| 127 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h new file mode 100644 index 000000000..5cf697efe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 16 | Int64 offset; | ||
| 17 | Int64 size; | ||
| 18 | s32 block_order; | ||
| 19 | std::array<u8, 4> reserved; | ||
| 20 | }; | ||
| 21 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>); | ||
| 22 | static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); | ||
| 23 | static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); | ||
| 24 | |||
| 25 | struct HierarchicalIntegrityVerificationInformation { | ||
| 26 | u32 max_layers; | ||
| 27 | std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info; | ||
| 28 | HashSalt seed; | ||
| 29 | |||
| 30 | s64 GetLayeredHashSize() const { | ||
| 31 | return this->info[this->max_layers - 2].offset; | ||
| 32 | } | ||
| 33 | |||
| 34 | s64 GetDataOffset() const { | ||
| 35 | return this->info[this->max_layers - 2].offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | s64 GetDataSize() const { | ||
| 39 | return this->info[this->max_layers - 2].size; | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>); | ||
| 43 | |||
| 44 | struct HierarchicalIntegrityVerificationMetaInformation { | ||
| 45 | u32 magic; | ||
| 46 | u32 version; | ||
| 47 | u32 master_hash_size; | ||
| 48 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 49 | }; | ||
| 50 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>); | ||
| 51 | |||
| 52 | struct HierarchicalIntegrityVerificationSizeSet { | ||
| 53 | s64 control_size; | ||
| 54 | s64 master_hash_size; | ||
| 55 | std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>); | ||
| 58 | |||
| 59 | class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 60 | YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage); | ||
| 61 | YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); | ||
| 62 | |||
| 63 | public: | ||
| 64 | using GenerateRandomFunction = void (*)(void* dst, size_t size); | ||
| 65 | |||
| 66 | class HierarchicalStorageInformation { | ||
| 67 | public: | ||
| 68 | enum { | ||
| 69 | MasterStorage = 0, | ||
| 70 | Layer1Storage = 1, | ||
| 71 | Layer2Storage = 2, | ||
| 72 | Layer3Storage = 3, | ||
| 73 | Layer4Storage = 4, | ||
| 74 | Layer5Storage = 5, | ||
| 75 | DataStorage = 6, | ||
| 76 | }; | ||
| 77 | |||
| 78 | private: | ||
| 79 | std::array<VirtualFile, DataStorage + 1> m_storages; | ||
| 80 | |||
| 81 | public: | ||
| 82 | void SetMasterHashStorage(VirtualFile s) { | ||
| 83 | m_storages[MasterStorage] = s; | ||
| 84 | } | ||
| 85 | void SetLayer1HashStorage(VirtualFile s) { | ||
| 86 | m_storages[Layer1Storage] = s; | ||
| 87 | } | ||
| 88 | void SetLayer2HashStorage(VirtualFile s) { | ||
| 89 | m_storages[Layer2Storage] = s; | ||
| 90 | } | ||
| 91 | void SetLayer3HashStorage(VirtualFile s) { | ||
| 92 | m_storages[Layer3Storage] = s; | ||
| 93 | } | ||
| 94 | void SetLayer4HashStorage(VirtualFile s) { | ||
| 95 | m_storages[Layer4Storage] = s; | ||
| 96 | } | ||
| 97 | void SetLayer5HashStorage(VirtualFile s) { | ||
| 98 | m_storages[Layer5Storage] = s; | ||
| 99 | } | ||
| 100 | void SetDataStorage(VirtualFile s) { | ||
| 101 | m_storages[DataStorage] = s; | ||
| 102 | } | ||
| 103 | |||
| 104 | VirtualFile& operator[](s32 index) { | ||
| 105 | ASSERT(MasterStorage <= index && index <= DataStorage); | ||
| 106 | return m_storages[index]; | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | |||
| 110 | public: | ||
| 111 | HierarchicalIntegrityVerificationStorage(); | ||
| 112 | virtual ~HierarchicalIntegrityVerificationStorage() override { | ||
| 113 | this->Finalize(); | ||
| 114 | } | ||
| 115 | |||
| 116 | Result Initialize(const HierarchicalIntegrityVerificationInformation& info, | ||
| 117 | HierarchicalStorageInformation storage, int max_data_cache_entries, | ||
| 118 | int max_hash_cache_entries, s8 buffer_level); | ||
| 119 | void Finalize(); | ||
| 120 | |||
| 121 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 122 | virtual size_t GetSize() const override; | ||
| 123 | |||
| 124 | bool IsInitialized() const { | ||
| 125 | return m_data_size >= 0; | ||
| 126 | } | ||
| 127 | |||
| 128 | s64 GetL1HashVerificationBlockSize() const { | ||
| 129 | return m_verify_storages[m_max_layers - 2]->GetBlockSize(); | ||
| 130 | } | ||
| 131 | |||
| 132 | VirtualFile GetL1HashStorage() { | ||
| 133 | return std::make_shared<OffsetVfsFile>( | ||
| 134 | m_buffer_storages[m_max_layers - 3], | ||
| 135 | Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0); | ||
| 136 | } | ||
| 137 | |||
| 138 | public: | ||
| 139 | static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { | ||
| 140 | return static_cast<s8>(16 + max_layers - 2); | ||
| 141 | } | ||
| 142 | |||
| 143 | protected: | ||
| 144 | static constexpr s64 HashSize = 256 / 8; | ||
| 145 | static constexpr size_t MaxLayers = IntegrityMaxLayerCount; | ||
| 146 | |||
| 147 | private: | ||
| 148 | static GenerateRandomFunction s_generate_random; | ||
| 149 | |||
| 150 | static void SetGenerateRandomFunction(GenerateRandomFunction func) { | ||
| 151 | s_generate_random = func; | ||
| 152 | } | ||
| 153 | |||
| 154 | private: | ||
| 155 | friend struct HierarchicalIntegrityVerificationMetaInformation; | ||
| 156 | |||
| 157 | private: | ||
| 158 | std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages; | ||
| 159 | std::array<VirtualFile, MaxLayers - 1> m_buffer_storages; | ||
| 160 | s64 m_data_size; | ||
| 161 | s32 m_max_layers; | ||
| 162 | }; | ||
| 163 | |||
| 164 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 000000000..caea0b8f8 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/scope_exit.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | s32 Log2(s32 value) { | ||
| 13 | ASSERT(value > 0); | ||
| 14 | ASSERT(Common::IsPowerOfTwo(value)); | ||
| 15 | |||
| 16 | s32 log = 0; | ||
| 17 | while ((value >>= 1) > 0) { | ||
| 18 | ++log; | ||
| 19 | } | ||
| 20 | return log; | ||
| 21 | } | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count, | ||
| 26 | size_t htbs, void* hash_buf, size_t hash_buf_size) { | ||
| 27 | // Validate preconditions. | ||
| 28 | ASSERT(layer_count == LayerCount); | ||
| 29 | ASSERT(Common::IsPowerOfTwo(htbs)); | ||
| 30 | ASSERT(hash_buf != nullptr); | ||
| 31 | |||
| 32 | // Set size tracking members. | ||
| 33 | m_hash_target_block_size = static_cast<s32>(htbs); | ||
| 34 | m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); | ||
| 35 | |||
| 36 | // Get the base storage size. | ||
| 37 | m_base_storage_size = base_storages[2]->GetSize(); | ||
| 38 | { | ||
| 39 | auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; }); | ||
| 40 | R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) | ||
| 41 | << m_log_size_ratio << m_log_size_ratio, | ||
| 42 | ResultHierarchicalSha256BaseStorageTooLarge); | ||
| 43 | size_guard.Cancel(); | ||
| 44 | } | ||
| 45 | |||
| 46 | // Set hash buffer tracking members. | ||
| 47 | m_base_storage = base_storages[2]; | ||
| 48 | m_hash_buffer = static_cast<char*>(hash_buf); | ||
| 49 | m_hash_buffer_size = hash_buf_size; | ||
| 50 | |||
| 51 | // Read the master hash. | ||
| 52 | std::array<u8, HashSize> master_hash{}; | ||
| 53 | base_storages[0]->ReadObject(std::addressof(master_hash)); | ||
| 54 | |||
| 55 | // Read and validate the data being hashed. | ||
| 56 | s64 hash_storage_size = base_storages[1]->GetSize(); | ||
| 57 | ASSERT(Common::IsAligned(hash_storage_size, HashSize)); | ||
| 58 | ASSERT(hash_storage_size <= m_hash_target_block_size); | ||
| 59 | ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size)); | ||
| 60 | |||
| 61 | base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer), | ||
| 62 | static_cast<size_t>(hash_storage_size), 0); | ||
| 63 | |||
| 64 | R_SUCCEED(); | ||
| 65 | } | ||
| 66 | |||
| 67 | size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 68 | // Succeed if zero-size. | ||
| 69 | if (size == 0) { | ||
| 70 | return size; | ||
| 71 | } | ||
| 72 | |||
| 73 | // Validate that we have a buffer to read into. | ||
| 74 | ASSERT(buffer != nullptr); | ||
| 75 | |||
| 76 | // Read the data. | ||
| 77 | return m_base_storage->Read(buffer, size, offset); | ||
| 78 | } | ||
| 79 | |||
| 80 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h new file mode 100644 index 000000000..18df400af --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class HierarchicalSha256Storage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(HierarchicalSha256Storage); | ||
| 16 | YUZU_NON_MOVEABLE(HierarchicalSha256Storage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr s32 LayerCount = 3; | ||
| 20 | static constexpr size_t HashSize = 256 / 8; | ||
| 21 | |||
| 22 | public: | ||
| 23 | HierarchicalSha256Storage() : m_mutex() {} | ||
| 24 | |||
| 25 | Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, | ||
| 26 | size_t hash_buf_size); | ||
| 27 | |||
| 28 | virtual size_t GetSize() const override { | ||
| 29 | return m_base_storage->GetSize(); | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual size_t Read(u8* buffer, size_t length, size_t offset) const override; | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_base_storage; | ||
| 36 | s64 m_base_storage_size; | ||
| 37 | char* m_hash_buffer; | ||
| 38 | size_t m_hash_buffer_size; | ||
| 39 | s32 m_hash_target_block_size; | ||
| 40 | s32 m_log_size_ratio; | ||
| 41 | std::mutex m_mutex; | ||
| 42 | }; | ||
| 43 | |||
| 44 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 000000000..7544e70b2 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | Result IndirectStorage::Initialize(VirtualFile table_storage) { | ||
| 10 | // Read and verify the bucket tree header. | ||
| 11 | BucketTree::Header header; | ||
| 12 | table_storage->ReadObject(std::addressof(header)); | ||
| 13 | R_TRY(header.Verify()); | ||
| 14 | |||
| 15 | // Determine extents. | ||
| 16 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 17 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 18 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 19 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 20 | |||
| 21 | // Initialize. | ||
| 22 | R_RETURN(this->Initialize( | ||
| 23 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 24 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 25 | header.entry_count)); | ||
| 26 | } | ||
| 27 | |||
| 28 | void IndirectStorage::Finalize() { | ||
| 29 | if (this->IsInitialized()) { | ||
| 30 | m_table.Finalize(); | ||
| 31 | for (auto i = 0; i < StorageCount; i++) { | ||
| 32 | m_data_storage[i] = VirtualFile(); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, | ||
| 38 | s64 offset, s64 size) { | ||
| 39 | // Validate pre-conditions. | ||
| 40 | ASSERT(offset >= 0); | ||
| 41 | ASSERT(size >= 0); | ||
| 42 | ASSERT(this->IsInitialized()); | ||
| 43 | |||
| 44 | // Clear the out count. | ||
| 45 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 46 | *out_entry_count = 0; | ||
| 47 | |||
| 48 | // Succeed if there's no range. | ||
| 49 | R_SUCCEED_IF(size == 0); | ||
| 50 | |||
| 51 | // If we have an output array, we need it to be non-null. | ||
| 52 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 53 | |||
| 54 | // Check that our range is valid. | ||
| 55 | BucketTree::Offsets table_offsets; | ||
| 56 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 57 | |||
| 58 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 59 | |||
| 60 | // Find the offset in our tree. | ||
| 61 | BucketTree::Visitor visitor; | ||
| 62 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 63 | { | ||
| 64 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 65 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 66 | ResultInvalidIndirectEntryOffset); | ||
| 67 | } | ||
| 68 | |||
| 69 | // Prepare to loop over entries. | ||
| 70 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 71 | s32 count = 0; | ||
| 72 | |||
| 73 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 74 | while (cur_entry.GetVirtualOffset() < end_offset) { | ||
| 75 | // Try to write the entry to the out list. | ||
| 76 | if (entry_count != 0) { | ||
| 77 | if (count >= entry_count) { | ||
| 78 | break; | ||
| 79 | } | ||
| 80 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 81 | } | ||
| 82 | |||
| 83 | count++; | ||
| 84 | |||
| 85 | // Advance. | ||
| 86 | if (visitor.CanMoveNext()) { | ||
| 87 | R_TRY(visitor.MoveNext()); | ||
| 88 | cur_entry = *visitor.Get<Entry>(); | ||
| 89 | } else { | ||
| 90 | break; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | // Write the output count. | ||
| 95 | *out_entry_count = count; | ||
| 96 | R_SUCCEED(); | ||
| 97 | } | ||
| 98 | |||
| 99 | size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 100 | // Validate pre-conditions. | ||
| 101 | ASSERT(this->IsInitialized()); | ||
| 102 | ASSERT(buffer != nullptr); | ||
| 103 | |||
| 104 | // Succeed if there's nothing to read. | ||
| 105 | if (size == 0) { | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>( | ||
| 110 | offset, size, | ||
| 111 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 112 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 113 | static_cast<size_t>(cur_size), data_offset); | ||
| 114 | R_SUCCEED(); | ||
| 115 | }); | ||
| 116 | |||
| 117 | return size; | ||
| 118 | } | ||
| 119 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h new file mode 100644 index 000000000..7854335bf --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h | |||
| @@ -0,0 +1,294 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | class IndirectStorage : public IReadOnlyStorage { | ||
| 16 | YUZU_NON_COPYABLE(IndirectStorage); | ||
| 17 | YUZU_NON_MOVEABLE(IndirectStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr s32 StorageCount = 2; | ||
| 21 | static constexpr size_t NodeSize = 16_KiB; | ||
| 22 | |||
| 23 | struct Entry { | ||
| 24 | std::array<u8, sizeof(s64)> virt_offset; | ||
| 25 | std::array<u8, sizeof(s64)> phys_offset; | ||
| 26 | s32 storage_index; | ||
| 27 | |||
| 28 | void SetVirtualOffset(const s64& ofs) { | ||
| 29 | std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64)); | ||
| 30 | } | ||
| 31 | |||
| 32 | s64 GetVirtualOffset() const { | ||
| 33 | s64 offset; | ||
| 34 | std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64)); | ||
| 35 | return offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | void SetPhysicalOffset(const s64& ofs) { | ||
| 39 | std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64)); | ||
| 40 | } | ||
| 41 | |||
| 42 | s64 GetPhysicalOffset() const { | ||
| 43 | s64 offset; | ||
| 44 | std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64)); | ||
| 45 | return offset; | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | static_assert(std::is_trivial_v<Entry>); | ||
| 49 | static_assert(sizeof(Entry) == 0x14); | ||
| 50 | |||
| 51 | struct EntryData { | ||
| 52 | s64 virt_offset; | ||
| 53 | s64 phys_offset; | ||
| 54 | s32 storage_index; | ||
| 55 | |||
| 56 | void Set(const Entry& entry) { | ||
| 57 | this->virt_offset = entry.GetVirtualOffset(); | ||
| 58 | this->phys_offset = entry.GetPhysicalOffset(); | ||
| 59 | this->storage_index = entry.storage_index; | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | static_assert(std::is_trivial_v<EntryData>); | ||
| 63 | |||
| 64 | public: | ||
| 65 | IndirectStorage() : m_table(), m_data_storage() {} | ||
| 66 | virtual ~IndirectStorage() { | ||
| 67 | this->Finalize(); | ||
| 68 | } | ||
| 69 | |||
| 70 | Result Initialize(VirtualFile table_storage); | ||
| 71 | void Finalize(); | ||
| 72 | |||
| 73 | bool IsInitialized() const { | ||
| 74 | return m_table.IsInitialized(); | ||
| 75 | } | ||
| 76 | |||
| 77 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) { | ||
| 78 | R_RETURN( | ||
| 79 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 80 | } | ||
| 81 | |||
| 82 | void SetStorage(s32 idx, VirtualFile storage) { | ||
| 83 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 84 | m_data_storage[idx] = storage; | ||
| 85 | } | ||
| 86 | |||
| 87 | template <typename T> | ||
| 88 | void SetStorage(s32 idx, T storage, s64 offset, s64 size) { | ||
| 89 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 90 | m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset); | ||
| 91 | } | ||
| 92 | |||
| 93 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 94 | s64 size); | ||
| 95 | |||
| 96 | virtual size_t GetSize() const override { | ||
| 97 | BucketTree::Offsets offsets{}; | ||
| 98 | m_table.GetOffsets(std::addressof(offsets)); | ||
| 99 | |||
| 100 | return offsets.end_offset; | ||
| 101 | } | ||
| 102 | |||
| 103 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 104 | |||
| 105 | public: | ||
| 106 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 107 | return BucketTree::QueryHeaderStorageSize(); | ||
| 108 | } | ||
| 109 | |||
| 110 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 111 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 112 | } | ||
| 113 | |||
| 114 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 115 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 116 | } | ||
| 117 | |||
| 118 | protected: | ||
| 119 | BucketTree& GetEntryTable() { | ||
| 120 | return m_table; | ||
| 121 | } | ||
| 122 | |||
| 123 | VirtualFile& GetDataStorage(s32 index) { | ||
| 124 | ASSERT(0 <= index && index < StorageCount); | ||
| 125 | return m_data_storage[index]; | ||
| 126 | } | ||
| 127 | |||
| 128 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 129 | Result OperatePerEntry(s64 offset, s64 size, F func); | ||
| 130 | |||
| 131 | private: | ||
| 132 | struct ContinuousReadingEntry { | ||
| 133 | static constexpr size_t FragmentSizeMax = 4_KiB; | ||
| 134 | |||
| 135 | IndirectStorage::Entry entry; | ||
| 136 | |||
| 137 | s64 GetVirtualOffset() const { | ||
| 138 | return this->entry.GetVirtualOffset(); | ||
| 139 | } | ||
| 140 | |||
| 141 | s64 GetPhysicalOffset() const { | ||
| 142 | return this->entry.GetPhysicalOffset(); | ||
| 143 | } | ||
| 144 | |||
| 145 | bool IsFragment() const { | ||
| 146 | return this->entry.storage_index != 0; | ||
| 147 | } | ||
| 148 | }; | ||
| 149 | static_assert(std::is_trivial_v<ContinuousReadingEntry>); | ||
| 150 | |||
| 151 | private: | ||
| 152 | mutable BucketTree m_table; | ||
| 153 | std::array<VirtualFile, StorageCount> m_data_storage; | ||
| 154 | }; | ||
| 155 | |||
| 156 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 157 | Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { | ||
| 158 | // Validate preconditions. | ||
| 159 | ASSERT(offset >= 0); | ||
| 160 | ASSERT(size >= 0); | ||
| 161 | ASSERT(this->IsInitialized()); | ||
| 162 | |||
| 163 | // Succeed if there's nothing to operate on. | ||
| 164 | R_SUCCEED_IF(size == 0); | ||
| 165 | |||
| 166 | // Get the table offsets. | ||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 169 | |||
| 170 | // Validate arguments. | ||
| 171 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 172 | |||
| 173 | // Find the offset in our tree. | ||
| 174 | BucketTree::Visitor visitor; | ||
| 175 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 176 | { | ||
| 177 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 178 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 179 | ResultInvalidIndirectEntryOffset); | ||
| 180 | } | ||
| 181 | |||
| 182 | // Prepare to operate in chunks. | ||
| 183 | auto cur_offset = offset; | ||
| 184 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 185 | BucketTree::ContinuousReadingInfo cr_info; | ||
| 186 | |||
| 187 | while (cur_offset < end_offset) { | ||
| 188 | // Get the current entry. | ||
| 189 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 190 | |||
| 191 | // Get and validate the entry's offset. | ||
| 192 | const auto cur_entry_offset = cur_entry.GetVirtualOffset(); | ||
| 193 | R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 194 | |||
| 195 | // Validate the storage index. | ||
| 196 | R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, | ||
| 197 | ResultInvalidIndirectEntryStorageIndex); | ||
| 198 | |||
| 199 | // If we need to check the continuous info, do so. | ||
| 200 | if constexpr (ContinuousCheck) { | ||
| 201 | // Scan, if we need to. | ||
| 202 | if (cr_info.CheckNeedScan()) { | ||
| 203 | R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>( | ||
| 204 | std::addressof(cr_info), cur_offset, | ||
| 205 | static_cast<size_t>(end_offset - cur_offset))); | ||
| 206 | } | ||
| 207 | |||
| 208 | // Process a base storage entry. | ||
| 209 | if (cr_info.CanDo()) { | ||
| 210 | // Ensure that we can process. | ||
| 211 | R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex); | ||
| 212 | |||
| 213 | // Ensure that we remain within range. | ||
| 214 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 215 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 216 | const auto cur_size = static_cast<s64>(cr_info.GetReadSize()); | ||
| 217 | |||
| 218 | // If we should, verify the range. | ||
| 219 | if constexpr (RangeCheck) { | ||
| 220 | // Get the current data storage's size. | ||
| 221 | s64 cur_data_storage_size = m_data_storage[0]->GetSize(); | ||
| 222 | |||
| 223 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 224 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 225 | ResultInvalidIndirectEntryOffset); | ||
| 226 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= | ||
| 227 | cur_data_storage_size, | ||
| 228 | ResultInvalidIndirectStorageSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | // Operate. | ||
| 232 | R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset, | ||
| 233 | cur_size)); | ||
| 234 | |||
| 235 | // Mark as done. | ||
| 236 | cr_info.Done(); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | // Get and validate the next entry offset. | ||
| 241 | s64 next_entry_offset; | ||
| 242 | if (visitor.CanMoveNext()) { | ||
| 243 | R_TRY(visitor.MoveNext()); | ||
| 244 | next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 245 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 246 | } else { | ||
| 247 | next_entry_offset = table_offsets.end_offset; | ||
| 248 | } | ||
| 249 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 250 | |||
| 251 | // Get the offset of the entry in the data we read. | ||
| 252 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 253 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 254 | ASSERT(data_size > 0); | ||
| 255 | |||
| 256 | // Determine how much is left. | ||
| 257 | const auto remaining_size = end_offset - cur_offset; | ||
| 258 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 259 | ASSERT(cur_size <= size); | ||
| 260 | |||
| 261 | // Operate, if we need to. | ||
| 262 | bool needs_operate; | ||
| 263 | if constexpr (!ContinuousCheck) { | ||
| 264 | needs_operate = true; | ||
| 265 | } else { | ||
| 266 | needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (needs_operate) { | ||
| 270 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 271 | |||
| 272 | if constexpr (RangeCheck) { | ||
| 273 | // Get the current data storage's size. | ||
| 274 | s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); | ||
| 275 | |||
| 276 | // Ensure that we remain within range. | ||
| 277 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 278 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 279 | ResultIndirectStorageCorrupted); | ||
| 280 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, | ||
| 281 | ResultIndirectStorageCorrupted); | ||
| 282 | } | ||
| 283 | |||
| 284 | R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset, | ||
| 285 | cur_offset, cur_size)); | ||
| 286 | } | ||
| 287 | |||
| 288 | cur_offset += cur_size; | ||
| 289 | } | ||
| 290 | |||
| 291 | R_SUCCEED(); | ||
| 292 | } | ||
| 293 | |||
| 294 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 000000000..2c3da230c --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | Result IntegrityRomFsStorage::Initialize( | ||
| 9 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 10 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 11 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 12 | // Set master hash. | ||
| 13 | m_master_hash = master_hash; | ||
| 14 | m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value); | ||
| 15 | R_UNLESS(m_master_hash_storage != nullptr, | ||
| 16 | ResultAllocationMemoryFailedInIntegrityRomFsStorageA); | ||
| 17 | |||
| 18 | // Set the master hash storage. | ||
| 19 | storage_info[0] = m_master_hash_storage; | ||
| 20 | |||
| 21 | // Initialize our integrity storage. | ||
| 22 | R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries, | ||
| 23 | max_hash_cache_entries, buffer_level)); | ||
| 24 | } | ||
| 25 | |||
| 26 | void IntegrityRomFsStorage::Finalize() { | ||
| 27 | m_integrity_storage.Finalize(); | ||
| 28 | } | ||
| 29 | |||
| 30 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h new file mode 100644 index 000000000..5f8512b2a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs_vector.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | constexpr inline size_t IntegrityLayerCountRomFs = 7; | ||
| 13 | constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB; | ||
| 14 | |||
| 15 | class IntegrityRomFsStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | IntegrityRomFsStorage() {} | ||
| 18 | virtual ~IntegrityRomFsStorage() override { | ||
| 19 | this->Finalize(); | ||
| 20 | } | ||
| 21 | |||
| 22 | Result Initialize( | ||
| 23 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 24 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 25 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 26 | void Finalize(); | ||
| 27 | |||
| 28 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 29 | return m_integrity_storage.Read(buffer, size, offset); | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual size_t GetSize() const override { | ||
| 33 | return m_integrity_storage.GetSize(); | ||
| 34 | } | ||
| 35 | |||
| 36 | private: | ||
| 37 | HierarchicalIntegrityVerificationStorage m_integrity_storage; | ||
| 38 | Hash m_master_hash; | ||
| 39 | std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp new file mode 100644 index 000000000..2f73abf86 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | constexpr inline u32 ILog2(u32 val) { | ||
| 10 | ASSERT(val > 0); | ||
| 11 | return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val)); | ||
| 12 | } | ||
| 13 | |||
| 14 | void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 15 | s64 upper_layer_verif_block_size, bool is_real_data) { | ||
| 16 | // Validate preconditions. | ||
| 17 | ASSERT(verif_block_size >= HashSize); | ||
| 18 | |||
| 19 | // Set storages. | ||
| 20 | m_hash_storage = hs; | ||
| 21 | m_data_storage = ds; | ||
| 22 | |||
| 23 | // Set verification block sizes. | ||
| 24 | m_verification_block_size = verif_block_size; | ||
| 25 | m_verification_block_order = ILog2(static_cast<u32>(verif_block_size)); | ||
| 26 | ASSERT(m_verification_block_size == 1ll << m_verification_block_order); | ||
| 27 | |||
| 28 | // Set upper layer block sizes. | ||
| 29 | upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); | ||
| 30 | m_upper_layer_verification_block_size = upper_layer_verif_block_size; | ||
| 31 | m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size)); | ||
| 32 | ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order); | ||
| 33 | |||
| 34 | // Validate sizes. | ||
| 35 | { | ||
| 36 | s64 hash_size = m_hash_storage->GetSize(); | ||
| 37 | s64 data_size = m_data_storage->GetSize(); | ||
| 38 | ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Set data. | ||
| 42 | m_is_real_data = is_real_data; | ||
| 43 | } | ||
| 44 | |||
| 45 | void IntegrityVerificationStorage::Finalize() { | ||
| 46 | m_hash_storage = VirtualFile(); | ||
| 47 | m_data_storage = VirtualFile(); | ||
| 48 | } | ||
| 49 | |||
| 50 | size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 51 | // Succeed if zero size. | ||
| 52 | if (size == 0) { | ||
| 53 | return size; | ||
| 54 | } | ||
| 55 | |||
| 56 | // Validate arguments. | ||
| 57 | ASSERT(buffer != nullptr); | ||
| 58 | |||
| 59 | // Validate the offset. | ||
| 60 | s64 data_size = m_data_storage->GetSize(); | ||
| 61 | ASSERT(offset <= static_cast<size_t>(data_size)); | ||
| 62 | |||
| 63 | // Validate the access range. | ||
| 64 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange( | ||
| 65 | offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))))); | ||
| 66 | |||
| 67 | // Determine the read extents. | ||
| 68 | size_t read_size = size; | ||
| 69 | if (static_cast<s64>(offset + read_size) > data_size) { | ||
| 70 | // Determine the padding sizes. | ||
| 71 | s64 padding_offset = data_size - offset; | ||
| 72 | size_t padding_size = static_cast<size_t>( | ||
| 73 | m_verification_block_size - (padding_offset & (m_verification_block_size - 1))); | ||
| 74 | ASSERT(static_cast<s64>(padding_size) < m_verification_block_size); | ||
| 75 | |||
| 76 | // Clear the padding. | ||
| 77 | std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size); | ||
| 78 | |||
| 79 | // Set the new in-bounds size. | ||
| 80 | read_size = static_cast<size_t>(data_size - offset); | ||
| 81 | } | ||
| 82 | |||
| 83 | // Perform the read. | ||
| 84 | return m_data_storage->Read(buffer, read_size, offset); | ||
| 85 | } | ||
| 86 | |||
| 87 | size_t IntegrityVerificationStorage::GetSize() const { | ||
| 88 | return m_data_storage->GetSize(); | ||
| 89 | } | ||
| 90 | |||
| 91 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h new file mode 100644 index 000000000..09f76799d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | class IntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 14 | YUZU_NON_COPYABLE(IntegrityVerificationStorage); | ||
| 15 | YUZU_NON_MOVEABLE(IntegrityVerificationStorage); | ||
| 16 | |||
| 17 | public: | ||
| 18 | static constexpr s64 HashSize = 256 / 8; | ||
| 19 | |||
| 20 | struct BlockHash { | ||
| 21 | std::array<u8, HashSize> hash; | ||
| 22 | }; | ||
| 23 | static_assert(std::is_trivial_v<BlockHash>); | ||
| 24 | |||
| 25 | public: | ||
| 26 | IntegrityVerificationStorage() | ||
| 27 | : m_verification_block_size(0), m_verification_block_order(0), | ||
| 28 | m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {} | ||
| 29 | virtual ~IntegrityVerificationStorage() override { | ||
| 30 | this->Finalize(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 34 | s64 upper_layer_verif_block_size, bool is_real_data); | ||
| 35 | void Finalize(); | ||
| 36 | |||
| 37 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 38 | virtual size_t GetSize() const override; | ||
| 39 | |||
| 40 | s64 GetBlockSize() const { | ||
| 41 | return m_verification_block_size; | ||
| 42 | } | ||
| 43 | |||
| 44 | private: | ||
| 45 | static void SetValidationBit(BlockHash* hash) { | ||
| 46 | ASSERT(hash != nullptr); | ||
| 47 | hash->hash[HashSize - 1] |= 0x80; | ||
| 48 | } | ||
| 49 | |||
| 50 | static bool IsValidationBit(const BlockHash* hash) { | ||
| 51 | ASSERT(hash != nullptr); | ||
| 52 | return (hash->hash[HashSize - 1] & 0x80) != 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | VirtualFile m_hash_storage; | ||
| 57 | VirtualFile m_data_storage; | ||
| 58 | s64 m_verification_block_size; | ||
| 59 | s64 m_verification_block_order; | ||
| 60 | s64 m_upper_layer_verification_block_size; | ||
| 61 | s64 m_upper_layer_verification_block_order; | ||
| 62 | bool m_is_real_data; | ||
| 63 | }; | ||
| 64 | |||
| 65 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h new file mode 100644 index 000000000..c07a127fb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class MemoryResourceBufferHoldStorage : public IStorage { | ||
| 11 | YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage); | ||
| 12 | YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size) | ||
| 16 | : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)), | ||
| 17 | m_buffer_size(buffer_size) {} | ||
| 18 | |||
| 19 | virtual ~MemoryResourceBufferHoldStorage() { | ||
| 20 | // If we have a buffer, deallocate it. | ||
| 21 | if (m_buffer != nullptr) { | ||
| 22 | ::operator delete(m_buffer); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | bool IsValid() const { | ||
| 27 | return m_buffer != nullptr; | ||
| 28 | } | ||
| 29 | void* GetBuffer() const { | ||
| 30 | return m_buffer; | ||
| 31 | } | ||
| 32 | |||
| 33 | public: | ||
| 34 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 35 | // Check pre-conditions. | ||
| 36 | ASSERT(m_storage != nullptr); | ||
| 37 | |||
| 38 | return m_storage->Read(buffer, size, offset); | ||
| 39 | } | ||
| 40 | |||
| 41 | virtual size_t GetSize() const override { | ||
| 42 | // Check pre-conditions. | ||
| 43 | ASSERT(m_storage != nullptr); | ||
| 44 | |||
| 45 | return m_storage->GetSize(); | ||
| 46 | } | ||
| 47 | |||
| 48 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 49 | // Check pre-conditions. | ||
| 50 | ASSERT(m_storage != nullptr); | ||
| 51 | |||
| 52 | return m_storage->Write(buffer, size, offset); | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | VirtualFile m_storage; | ||
| 57 | void* m_buffer; | ||
| 58 | size_t m_buffer_size; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..0f5432203 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp | |||
| @@ -0,0 +1,1351 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_compressed_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 13 | #include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" | ||
| 14 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 15 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 16 | #include "core/file_sys/fssystem/fssystem_switch_storage.h" | ||
| 17 | #include "core/file_sys/vfs_offset.h" | ||
| 18 | #include "core/file_sys/vfs_vector.h" | ||
| 19 | |||
| 20 | namespace FileSys { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | constexpr inline s32 IntegrityDataCacheCount = 24; | ||
| 25 | constexpr inline s32 IntegrityHashCacheCount = 8; | ||
| 26 | |||
| 27 | constexpr inline s32 IntegrityDataCacheCountForMeta = 16; | ||
| 28 | constexpr inline s32 IntegrityHashCacheCountForMeta = 2; | ||
| 29 | |||
| 30 | class SharedNcaBodyStorage : public IReadOnlyStorage { | ||
| 31 | YUZU_NON_COPYABLE(SharedNcaBodyStorage); | ||
| 32 | YUZU_NON_MOVEABLE(SharedNcaBodyStorage); | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_storage; | ||
| 36 | std::shared_ptr<NcaReader> m_nca_reader; | ||
| 37 | |||
| 38 | public: | ||
| 39 | SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) | ||
| 40 | : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} | ||
| 41 | |||
| 42 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 43 | // Validate pre-conditions. | ||
| 44 | ASSERT(m_storage != nullptr); | ||
| 45 | |||
| 46 | // Read from the base storage. | ||
| 47 | return m_storage->Read(buffer, size, offset); | ||
| 48 | } | ||
| 49 | |||
| 50 | virtual size_t GetSize() const override { | ||
| 51 | // Validate pre-conditions. | ||
| 52 | ASSERT(m_storage != nullptr); | ||
| 53 | |||
| 54 | return m_storage->GetSize(); | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { | ||
| 59 | return static_cast<s64>(reader.GetFsOffset(fs_index)); | ||
| 60 | } | ||
| 61 | |||
| 62 | inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { | ||
| 63 | return static_cast<s64>(reader.GetFsEndOffset(fs_index)); | ||
| 64 | } | ||
| 65 | |||
| 66 | using Sha256DataRegion = NcaFsHeader::Region; | ||
| 67 | using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; | ||
| 68 | using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; | ||
| 69 | |||
| 70 | } // namespace | ||
| 71 | |||
| 72 | Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, | ||
| 73 | NcaFsHeaderReader* out_header_reader, | ||
| 74 | s32 fs_index, StorageContext* ctx) { | ||
| 75 | // Open storage. | ||
| 76 | R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); | ||
| 77 | } | ||
| 78 | |||
| 79 | Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 80 | s32 fs_index, StorageContext* ctx) { | ||
| 81 | // Validate preconditions. | ||
| 82 | ASSERT(out != nullptr); | ||
| 83 | ASSERT(out_header_reader != nullptr); | ||
| 84 | ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); | ||
| 85 | |||
| 86 | // Validate the fs index. | ||
| 87 | R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); | ||
| 88 | |||
| 89 | // Initialize our header reader for the fs index. | ||
| 90 | R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); | ||
| 91 | |||
| 92 | // Declare the storage we're opening. | ||
| 93 | VirtualFile storage; | ||
| 94 | |||
| 95 | // Process sparse layer. | ||
| 96 | s64 fs_data_offset = 0; | ||
| 97 | if (out_header_reader->ExistsSparseLayer()) { | ||
| 98 | // Get the sparse info. | ||
| 99 | const auto& sparse_info = out_header_reader->GetSparseInfo(); | ||
| 100 | |||
| 101 | // Create based on whether we have a meta hash layer. | ||
| 102 | if (out_header_reader->ExistsSparseMetaHashLayer()) { | ||
| 103 | // Create the sparse storage with verification. | ||
| 104 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 105 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 106 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 107 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 108 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 109 | out_header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 110 | out_header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 111 | out_header_reader->GetSparseMetaHashType())); | ||
| 112 | } else { | ||
| 113 | // Create the sparse storage. | ||
| 114 | R_TRY(this->CreateSparseStorage( | ||
| 115 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 116 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 117 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 118 | fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 119 | } | ||
| 120 | } else { | ||
| 121 | // Get the data offsets. | ||
| 122 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 123 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 124 | |||
| 125 | // Validate that we're within range. | ||
| 126 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 127 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 128 | |||
| 129 | // Create the body substorage. | ||
| 130 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 131 | |||
| 132 | // Potentially save the body substorage to our context. | ||
| 133 | if (ctx != nullptr) { | ||
| 134 | ctx->body_substorage = storage; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | // Process patch layer. | ||
| 139 | const auto& patch_info = out_header_reader->GetPatchInfo(); | ||
| 140 | VirtualFile patch_meta_aes_ctr_ex_meta_storage; | ||
| 141 | VirtualFile patch_meta_indirect_meta_storage; | ||
| 142 | if (out_header_reader->ExistsPatchMetaHashLayer()) { | ||
| 143 | // Check the meta hash type. | ||
| 144 | R_UNLESS(out_header_reader->GetPatchMetaHashType() == | ||
| 145 | NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 146 | ResultRomNcaInvalidPatchMetaDataHashType); | ||
| 147 | |||
| 148 | // Create the patch meta storage. | ||
| 149 | R_TRY(this->CreatePatchMetaStorage( | ||
| 150 | std::addressof(patch_meta_aes_ctr_ex_meta_storage), | ||
| 151 | std::addressof(patch_meta_indirect_meta_storage), | ||
| 152 | ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, | ||
| 153 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, | ||
| 154 | out_header_reader->GetPatchMetaDataHashDataInfo())); | ||
| 155 | } | ||
| 156 | |||
| 157 | if (patch_info.HasAesCtrExTable()) { | ||
| 158 | // Check the encryption type. | ||
| 159 | ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || | ||
| 160 | out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 161 | out_header_reader->GetEncryptionType() == | ||
| 162 | NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 163 | |||
| 164 | // Create the ex meta storage. | ||
| 165 | VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; | ||
| 166 | if (aes_ctr_ex_storage_meta_storage == nullptr) { | ||
| 167 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 168 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 169 | |||
| 170 | R_TRY(this->CreateAesCtrExStorageMetaStorage( | ||
| 171 | std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, | ||
| 172 | out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), | ||
| 173 | patch_info)); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Create the ex storage. | ||
| 177 | VirtualFile aes_ctr_ex_storage; | ||
| 178 | R_TRY(this->CreateAesCtrExStorage( | ||
| 179 | std::addressof(aes_ctr_ex_storage), | ||
| 180 | ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), | ||
| 181 | aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 182 | patch_info)); | ||
| 183 | |||
| 184 | // Set the base storage as the ex storage. | ||
| 185 | storage = std::move(aes_ctr_ex_storage); | ||
| 186 | |||
| 187 | // Potentially save storages to our context. | ||
| 188 | if (ctx != nullptr) { | ||
| 189 | ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; | ||
| 190 | ctx->aes_ctr_ex_storage_data_storage = storage; | ||
| 191 | ctx->fs_data_storage = storage; | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | // Create the appropriate storage for the encryption type. | ||
| 195 | switch (out_header_reader->GetEncryptionType()) { | ||
| 196 | case NcaFsHeader::EncryptionType::None: | ||
| 197 | // If there's no encryption, use the base storage we made previously. | ||
| 198 | break; | ||
| 199 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 200 | R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), | ||
| 201 | fs_data_offset)); | ||
| 202 | break; | ||
| 203 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 204 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), | ||
| 205 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 206 | AlignmentStorageRequirement::None)); | ||
| 207 | break; | ||
| 208 | case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { | ||
| 209 | // Create the aes ctr storage. | ||
| 210 | VirtualFile aes_ctr_storage; | ||
| 211 | R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, | ||
| 212 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 213 | AlignmentStorageRequirement::None)); | ||
| 214 | |||
| 215 | // Create region switch storage. | ||
| 216 | R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, | ||
| 217 | std::move(storage), std::move(aes_ctr_storage))); | ||
| 218 | } break; | ||
| 219 | default: | ||
| 220 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 221 | } | ||
| 222 | |||
| 223 | // Potentially save storages to our context. | ||
| 224 | if (ctx != nullptr) { | ||
| 225 | ctx->fs_data_storage = storage; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | // Process indirect layer. | ||
| 230 | if (patch_info.HasIndirectTable()) { | ||
| 231 | // Create the indirect meta storage. | ||
| 232 | VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; | ||
| 233 | if (indirect_storage_meta_storage == nullptr) { | ||
| 234 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 235 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 236 | |||
| 237 | R_TRY(this->CreateIndirectStorageMetaStorage( | ||
| 238 | std::addressof(indirect_storage_meta_storage), storage, patch_info)); | ||
| 239 | } | ||
| 240 | |||
| 241 | // Potentially save the indirect meta storage to our context. | ||
| 242 | if (ctx != nullptr) { | ||
| 243 | ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; | ||
| 244 | } | ||
| 245 | |||
| 246 | // Get the original indirectable storage. | ||
| 247 | VirtualFile original_indirectable_storage; | ||
| 248 | if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { | ||
| 249 | // Create a driver for the original. | ||
| 250 | NcaFileSystemDriver original_driver(m_original_reader); | ||
| 251 | |||
| 252 | // Create a header reader for the original. | ||
| 253 | NcaFsHeaderReader original_header_reader; | ||
| 254 | R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); | ||
| 255 | |||
| 256 | // Open original indirectable storage. | ||
| 257 | R_TRY(original_driver.OpenIndirectableStorageAsOriginal( | ||
| 258 | std::addressof(original_indirectable_storage), | ||
| 259 | std::addressof(original_header_reader), ctx)); | ||
| 260 | } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { | ||
| 261 | // Use the external original storage. | ||
| 262 | original_indirectable_storage = ctx->external_original_storage; | ||
| 263 | } else { | ||
| 264 | // Allocate a dummy memory storage as original storage. | ||
| 265 | original_indirectable_storage = std::make_shared<VectorVfsFile>(); | ||
| 266 | R_UNLESS(original_indirectable_storage != nullptr, | ||
| 267 | ResultAllocationMemoryFailedAllocateShared); | ||
| 268 | } | ||
| 269 | |||
| 270 | // Create the indirect storage. | ||
| 271 | VirtualFile indirect_storage; | ||
| 272 | R_TRY(this->CreateIndirectStorage( | ||
| 273 | std::addressof(indirect_storage), | ||
| 274 | ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), | ||
| 275 | std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), | ||
| 276 | patch_info)); | ||
| 277 | |||
| 278 | // Set storage as the indirect storage. | ||
| 279 | storage = std::move(indirect_storage); | ||
| 280 | } | ||
| 281 | |||
| 282 | // Check if we're sparse or requested to skip the integrity layer. | ||
| 283 | if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { | ||
| 284 | *out = std::move(storage); | ||
| 285 | R_SUCCEED(); | ||
| 286 | } | ||
| 287 | |||
| 288 | // Create the non-raw storage. | ||
| 289 | R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); | ||
| 290 | } | ||
| 291 | |||
| 292 | Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, | ||
| 293 | const NcaFsHeaderReader* header_reader, | ||
| 294 | VirtualFile raw_storage, | ||
| 295 | StorageContext* ctx) { | ||
| 296 | // Initialize storage as raw storage. | ||
| 297 | VirtualFile storage = std::move(raw_storage); | ||
| 298 | |||
| 299 | // Process hash/integrity layer. | ||
| 300 | switch (header_reader->GetHashType()) { | ||
| 301 | case NcaFsHeader::HashType::HierarchicalSha256Hash: | ||
| 302 | R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), | ||
| 303 | header_reader->GetHashData().hierarchical_sha256_data)); | ||
| 304 | break; | ||
| 305 | case NcaFsHeader::HashType::HierarchicalIntegrityHash: | ||
| 306 | R_TRY(this->CreateIntegrityVerificationStorage( | ||
| 307 | std::addressof(storage), std::move(storage), | ||
| 308 | header_reader->GetHashData().integrity_meta_info)); | ||
| 309 | break; | ||
| 310 | default: | ||
| 311 | R_THROW(ResultInvalidNcaFsHeaderHashType); | ||
| 312 | } | ||
| 313 | |||
| 314 | // Process compression layer. | ||
| 315 | if (header_reader->ExistsCompressionLayer()) { | ||
| 316 | R_TRY(this->CreateCompressedStorage( | ||
| 317 | std::addressof(storage), | ||
| 318 | ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, | ||
| 319 | ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, | ||
| 320 | std::move(storage), header_reader->GetCompressionInfo())); | ||
| 321 | } | ||
| 322 | |||
| 323 | // Set output storage. | ||
| 324 | *out = std::move(storage); | ||
| 325 | R_SUCCEED(); | ||
| 326 | } | ||
| 327 | |||
| 328 | Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( | ||
| 329 | VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { | ||
| 330 | // Get the fs index. | ||
| 331 | const auto fs_index = header_reader->GetFsIndex(); | ||
| 332 | |||
| 333 | // Declare the storage we're opening. | ||
| 334 | VirtualFile storage; | ||
| 335 | |||
| 336 | // Process sparse layer. | ||
| 337 | s64 fs_data_offset = 0; | ||
| 338 | if (header_reader->ExistsSparseLayer()) { | ||
| 339 | // Get the sparse info. | ||
| 340 | const auto& sparse_info = header_reader->GetSparseInfo(); | ||
| 341 | |||
| 342 | // Create based on whether we have a meta hash layer. | ||
| 343 | if (header_reader->ExistsSparseMetaHashLayer()) { | ||
| 344 | // Create the sparse storage with verification. | ||
| 345 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 346 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 347 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 348 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 349 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 350 | header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 351 | header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 352 | header_reader->GetSparseMetaHashType())); | ||
| 353 | } else { | ||
| 354 | // Create the sparse storage. | ||
| 355 | R_TRY(this->CreateSparseStorage( | ||
| 356 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 357 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 358 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 359 | fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 360 | } | ||
| 361 | } else { | ||
| 362 | // Get the data offsets. | ||
| 363 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 364 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 365 | |||
| 366 | // Validate that we're within range. | ||
| 367 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 368 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 369 | |||
| 370 | // Create the body substorage. | ||
| 371 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 372 | } | ||
| 373 | |||
| 374 | // Create the appropriate storage for the encryption type. | ||
| 375 | switch (header_reader->GetEncryptionType()) { | ||
| 376 | case NcaFsHeader::EncryptionType::None: | ||
| 377 | // If there's no encryption, use the base storage we made previously. | ||
| 378 | break; | ||
| 379 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 380 | R_TRY( | ||
| 381 | this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); | ||
| 382 | break; | ||
| 383 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 384 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, | ||
| 385 | header_reader->GetAesCtrUpperIv(), | ||
| 386 | AlignmentStorageRequirement::CacheBlockSize)); | ||
| 387 | break; | ||
| 388 | default: | ||
| 389 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 390 | } | ||
| 391 | |||
| 392 | // Set output storage. | ||
| 393 | *out = std::move(storage); | ||
| 394 | R_SUCCEED(); | ||
| 395 | } | ||
| 396 | |||
| 397 | Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { | ||
| 398 | // Create the body storage. | ||
| 399 | auto body_storage = | ||
| 400 | std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); | ||
| 401 | R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 402 | |||
| 403 | // Get the body storage size. | ||
| 404 | s64 body_size = body_storage->GetSize(); | ||
| 405 | |||
| 406 | // Check that we're within range. | ||
| 407 | R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 408 | |||
| 409 | // Create substorage. | ||
| 410 | auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); | ||
| 411 | R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 412 | |||
| 413 | // Set the output storage. | ||
| 414 | *out = std::move(body_substorage); | ||
| 415 | R_SUCCEED(); | ||
| 416 | } | ||
| 417 | |||
| 418 | Result NcaFileSystemDriver::CreateAesCtrStorage( | ||
| 419 | VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 420 | AlignmentStorageRequirement alignment_storage_requirement) { | ||
| 421 | // Check pre-conditions. | ||
| 422 | ASSERT(out != nullptr); | ||
| 423 | ASSERT(base_storage != nullptr); | ||
| 424 | |||
| 425 | // Create the iv. | ||
| 426 | std::array<u8, AesCtrStorage::IvSize> iv{}; | ||
| 427 | AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); | ||
| 428 | |||
| 429 | // Create the ctr storage. | ||
| 430 | VirtualFile aes_ctr_storage; | ||
| 431 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 432 | aes_ctr_storage = std::make_shared<AesCtrStorage>( | ||
| 433 | std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 434 | iv.data(), AesCtrStorage::IvSize); | ||
| 435 | R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 436 | } else { | ||
| 437 | // Create software decryption storage. | ||
| 438 | auto sw_storage = std::make_shared<AesCtrStorage>( | ||
| 439 | base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 440 | AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); | ||
| 441 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 442 | |||
| 443 | aes_ctr_storage = std::move(sw_storage); | ||
| 444 | } | ||
| 445 | |||
| 446 | // Create alignment matching storage. | ||
| 447 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( | ||
| 448 | std::move(aes_ctr_storage)); | ||
| 449 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 450 | |||
| 451 | // Set the out storage. | ||
| 452 | *out = std::move(aligned_storage); | ||
| 453 | R_SUCCEED(); | ||
| 454 | } | ||
| 455 | |||
| 456 | Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 457 | s64 offset) { | ||
| 458 | // Check pre-conditions. | ||
| 459 | ASSERT(out != nullptr); | ||
| 460 | ASSERT(base_storage != nullptr); | ||
| 461 | |||
| 462 | // Create the iv. | ||
| 463 | std::array<u8, AesXtsStorage::IvSize> iv{}; | ||
| 464 | AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); | ||
| 465 | |||
| 466 | // Make the aes xts storage. | ||
| 467 | const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); | ||
| 468 | const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); | ||
| 469 | auto xts_storage = | ||
| 470 | std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, | ||
| 471 | iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); | ||
| 472 | R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 473 | |||
| 474 | // Create alignment matching storage. | ||
| 475 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( | ||
| 476 | std::move(xts_storage)); | ||
| 477 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 478 | |||
| 479 | // Set the out storage. | ||
| 480 | *out = std::move(xts_storage); | ||
| 481 | R_SUCCEED(); | ||
| 482 | } | ||
| 483 | |||
| 484 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, | ||
| 485 | VirtualFile base_storage, s64 offset, | ||
| 486 | const NcaAesCtrUpperIv& upper_iv, | ||
| 487 | const NcaSparseInfo& sparse_info) { | ||
| 488 | // Validate preconditions. | ||
| 489 | ASSERT(out != nullptr); | ||
| 490 | ASSERT(base_storage != nullptr); | ||
| 491 | |||
| 492 | // Get the base storage size. | ||
| 493 | s64 base_size = base_storage->GetSize(); | ||
| 494 | |||
| 495 | // Get the meta extents. | ||
| 496 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 497 | const auto meta_size = sparse_info.bucket.size; | ||
| 498 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 499 | |||
| 500 | // Create the encrypted storage. | ||
| 501 | auto enc_storage = | ||
| 502 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 503 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 504 | |||
| 505 | // Create the decrypted storage. | ||
| 506 | VirtualFile decrypted_storage; | ||
| 507 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 508 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 509 | AlignmentStorageRequirement::None)); | ||
| 510 | |||
| 511 | // Create buffered storage. | ||
| 512 | std::vector<u8> meta_data(meta_size); | ||
| 513 | decrypted_storage->Read(meta_data.data(), meta_size, 0); | ||
| 514 | |||
| 515 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 516 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 517 | |||
| 518 | // Set the output. | ||
| 519 | *out = std::move(buffered_storage); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, | ||
| 524 | VirtualFile base_storage, s64 base_size, | ||
| 525 | VirtualFile meta_storage, | ||
| 526 | const NcaSparseInfo& sparse_info, | ||
| 527 | bool external_info) { | ||
| 528 | // Validate preconditions. | ||
| 529 | ASSERT(out != nullptr); | ||
| 530 | ASSERT(base_storage != nullptr); | ||
| 531 | ASSERT(meta_storage != nullptr); | ||
| 532 | |||
| 533 | // Read and verify the bucket tree header. | ||
| 534 | BucketTree::Header header; | ||
| 535 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 536 | R_TRY(header.Verify()); | ||
| 537 | |||
| 538 | // Determine storage extents. | ||
| 539 | const auto node_offset = 0; | ||
| 540 | const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); | ||
| 541 | const auto entry_offset = node_offset + node_size; | ||
| 542 | const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); | ||
| 543 | |||
| 544 | // Create the sparse storage. | ||
| 545 | auto sparse_storage = std::make_shared<SparseStorage>(); | ||
| 546 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 547 | |||
| 548 | // Sanity check that we can be doing this. | ||
| 549 | ASSERT(header.entry_count != 0); | ||
| 550 | |||
| 551 | // Initialize the sparse storage. | ||
| 552 | R_TRY(sparse_storage->Initialize( | ||
| 553 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), | ||
| 554 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), | ||
| 555 | header.entry_count)); | ||
| 556 | |||
| 557 | // If not external, set the data storage. | ||
| 558 | if (!external_info) { | ||
| 559 | sparse_storage->SetDataStorage( | ||
| 560 | std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); | ||
| 561 | } | ||
| 562 | |||
| 563 | // Set the output. | ||
| 564 | *out = std::move(sparse_storage); | ||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 569 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 570 | VirtualFile* out_meta_storage, s32 index, | ||
| 571 | const NcaAesCtrUpperIv& upper_iv, | ||
| 572 | const NcaSparseInfo& sparse_info) { | ||
| 573 | // Validate preconditions. | ||
| 574 | ASSERT(out != nullptr); | ||
| 575 | ASSERT(out_fs_data_offset != nullptr); | ||
| 576 | |||
| 577 | // Check the sparse info generation. | ||
| 578 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 579 | |||
| 580 | // Read and verify the bucket tree header. | ||
| 581 | BucketTree::Header header; | ||
| 582 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 583 | R_TRY(header.Verify()); | ||
| 584 | |||
| 585 | // Determine the storage extents. | ||
| 586 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 587 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 588 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 589 | |||
| 590 | // Create the sparse storage. | ||
| 591 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 592 | if (header.entry_count != 0) { | ||
| 593 | // Create the body substorage. | ||
| 594 | VirtualFile body_substorage; | ||
| 595 | R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), | ||
| 596 | sparse_info.physical_offset, | ||
| 597 | sparse_info.GetPhysicalSize())); | ||
| 598 | |||
| 599 | // Create the meta storage. | ||
| 600 | VirtualFile meta_storage; | ||
| 601 | R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, | ||
| 602 | sparse_info.physical_offset, upper_iv, | ||
| 603 | sparse_info)); | ||
| 604 | |||
| 605 | // Potentially set the output meta storage. | ||
| 606 | if (out_meta_storage != nullptr) { | ||
| 607 | *out_meta_storage = meta_storage; | ||
| 608 | } | ||
| 609 | |||
| 610 | // Create the sparse storage. | ||
| 611 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 612 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 613 | sparse_info, false)); | ||
| 614 | } else { | ||
| 615 | // If there are no entries, there's nothing to actually do. | ||
| 616 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 617 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 618 | |||
| 619 | sparse_storage->Initialize(fs_size); | ||
| 620 | } | ||
| 621 | |||
| 622 | // Potentially set the output sparse storage. | ||
| 623 | if (out_sparse_storage != nullptr) { | ||
| 624 | *out_sparse_storage = sparse_storage; | ||
| 625 | } | ||
| 626 | |||
| 627 | // Set the output fs data offset. | ||
| 628 | *out_fs_data_offset = fs_offset; | ||
| 629 | |||
| 630 | // Set the output storage. | ||
| 631 | *out = std::move(sparse_storage); | ||
| 632 | R_SUCCEED(); | ||
| 633 | } | ||
| 634 | |||
| 635 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( | ||
| 636 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 637 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 638 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 639 | // Validate preconditions. | ||
| 640 | ASSERT(out != nullptr); | ||
| 641 | ASSERT(base_storage != nullptr); | ||
| 642 | |||
| 643 | // Get the base storage size. | ||
| 644 | s64 base_size = base_storage->GetSize(); | ||
| 645 | |||
| 646 | // Get the meta extents. | ||
| 647 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 648 | const auto meta_size = sparse_info.bucket.size; | ||
| 649 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 650 | |||
| 651 | // Get the meta data hash data extents. | ||
| 652 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 653 | const s64 meta_data_hash_data_size = | ||
| 654 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 655 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 656 | ResultNcaBaseStorageOutOfRangeB); | ||
| 657 | |||
| 658 | // Check that the meta is before the hash data. | ||
| 659 | R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, | ||
| 660 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 661 | |||
| 662 | // Check that offsets are appropriately aligned. | ||
| 663 | R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), | ||
| 664 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 665 | R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), | ||
| 666 | ResultInvalidNcaFsHeader); | ||
| 667 | |||
| 668 | // Create the meta storage. | ||
| 669 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 670 | std::move(base_storage), | ||
| 671 | meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); | ||
| 672 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 673 | |||
| 674 | // Create the decrypted storage. | ||
| 675 | VirtualFile decrypted_storage; | ||
| 676 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 677 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 678 | AlignmentStorageRequirement::None)); | ||
| 679 | |||
| 680 | // Create the verification storage. | ||
| 681 | VirtualFile integrity_storage; | ||
| 682 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 683 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 684 | meta_offset, meta_data_hash_data_info); | ||
| 685 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 686 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); | ||
| 687 | } | ||
| 688 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 689 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); | ||
| 690 | } | ||
| 691 | R_TRY(rc); | ||
| 692 | |||
| 693 | // Create the meta storage. | ||
| 694 | auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); | ||
| 695 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 696 | |||
| 697 | // Set the output. | ||
| 698 | *out = std::move(meta_storage); | ||
| 699 | R_SUCCEED(); | ||
| 700 | } | ||
| 701 | |||
| 702 | Result NcaFileSystemDriver::CreateSparseStorageWithVerification( | ||
| 703 | VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 704 | VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, | ||
| 705 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 706 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 707 | NcaFsHeader::MetaDataHashType meta_data_hash_type) { | ||
| 708 | // Validate preconditions. | ||
| 709 | ASSERT(out != nullptr); | ||
| 710 | ASSERT(out_fs_data_offset != nullptr); | ||
| 711 | |||
| 712 | // Check the sparse info generation. | ||
| 713 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 714 | |||
| 715 | // Read and verify the bucket tree header. | ||
| 716 | BucketTree::Header header; | ||
| 717 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 718 | R_TRY(header.Verify()); | ||
| 719 | |||
| 720 | // Determine the storage extents. | ||
| 721 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 722 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 723 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 724 | |||
| 725 | // Create the sparse storage. | ||
| 726 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 727 | if (header.entry_count != 0) { | ||
| 728 | // Create the body substorage. | ||
| 729 | VirtualFile body_substorage; | ||
| 730 | R_TRY(this->CreateBodySubStorage( | ||
| 731 | std::addressof(body_substorage), sparse_info.physical_offset, | ||
| 732 | Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + | ||
| 733 | static_cast<s64>(meta_data_hash_data_info.size), | ||
| 734 | NcaHeader::CtrBlockSize))); | ||
| 735 | |||
| 736 | // Check the meta data hash type. | ||
| 737 | R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 738 | ResultRomNcaInvalidSparseMetaDataHashType); | ||
| 739 | |||
| 740 | // Create the meta storage. | ||
| 741 | VirtualFile meta_storage; | ||
| 742 | R_TRY(this->CreateSparseStorageMetaStorageWithVerification( | ||
| 743 | std::addressof(meta_storage), out_layer_info_storage, body_substorage, | ||
| 744 | sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); | ||
| 745 | |||
| 746 | // Potentially set the output meta storage. | ||
| 747 | if (out_meta_storage != nullptr) { | ||
| 748 | *out_meta_storage = meta_storage; | ||
| 749 | } | ||
| 750 | |||
| 751 | // Create the sparse storage. | ||
| 752 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 753 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 754 | sparse_info, false)); | ||
| 755 | } else { | ||
| 756 | // If there are no entries, there's nothing to actually do. | ||
| 757 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 758 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 759 | |||
| 760 | sparse_storage->Initialize(fs_size); | ||
| 761 | } | ||
| 762 | |||
| 763 | // Potentially set the output sparse storage. | ||
| 764 | if (out_sparse_storage != nullptr) { | ||
| 765 | *out_sparse_storage = sparse_storage; | ||
| 766 | } | ||
| 767 | |||
| 768 | // Set the output fs data offset. | ||
| 769 | *out_fs_data_offset = fs_offset; | ||
| 770 | |||
| 771 | // Set the output storage. | ||
| 772 | *out = std::move(sparse_storage); | ||
| 773 | R_SUCCEED(); | ||
| 774 | } | ||
| 775 | |||
| 776 | Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( | ||
| 777 | VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 778 | NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, | ||
| 779 | const NcaPatchInfo& patch_info) { | ||
| 780 | // Validate preconditions. | ||
| 781 | ASSERT(out != nullptr); | ||
| 782 | ASSERT(base_storage != nullptr); | ||
| 783 | ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || | ||
| 784 | encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 785 | encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 786 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 787 | |||
| 788 | // Validate patch info extents. | ||
| 789 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 790 | R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 791 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 792 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 793 | |||
| 794 | // Get the base storage size. | ||
| 795 | s64 base_size = base_storage->GetSize(); | ||
| 796 | |||
| 797 | // Get and validate the meta extents. | ||
| 798 | const s64 meta_offset = patch_info.aes_ctr_ex_offset; | ||
| 799 | const s64 meta_size = | ||
| 800 | Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); | ||
| 801 | R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 802 | |||
| 803 | // Create the encrypted storage. | ||
| 804 | auto enc_storage = | ||
| 805 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 806 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 807 | |||
| 808 | // Create the decrypted storage. | ||
| 809 | VirtualFile decrypted_storage; | ||
| 810 | if (encryption_type != NcaFsHeader::EncryptionType::None) { | ||
| 811 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 812 | offset + meta_offset, upper_iv, | ||
| 813 | AlignmentStorageRequirement::None)); | ||
| 814 | } else { | ||
| 815 | // If encryption type is none, don't do any decryption. | ||
| 816 | decrypted_storage = std::move(enc_storage); | ||
| 817 | } | ||
| 818 | |||
| 819 | // Create meta storage. | ||
| 820 | auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); | ||
| 821 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 822 | |||
| 823 | // Create buffered storage. | ||
| 824 | std::vector<u8> meta_data(meta_size); | ||
| 825 | meta_storage->Read(meta_data.data(), meta_size, 0); | ||
| 826 | |||
| 827 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 828 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 829 | |||
| 830 | // Set the output. | ||
| 831 | *out = std::move(buffered_storage); | ||
| 832 | R_SUCCEED(); | ||
| 833 | } | ||
| 834 | |||
| 835 | Result NcaFileSystemDriver::CreateAesCtrExStorage( | ||
| 836 | VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 837 | VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, | ||
| 838 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { | ||
| 839 | // Validate pre-conditions. | ||
| 840 | ASSERT(out != nullptr); | ||
| 841 | ASSERT(base_storage != nullptr); | ||
| 842 | ASSERT(meta_storage != nullptr); | ||
| 843 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 844 | |||
| 845 | // Read the bucket tree header. | ||
| 846 | BucketTree::Header header; | ||
| 847 | std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); | ||
| 848 | R_TRY(header.Verify()); | ||
| 849 | |||
| 850 | // Determine the bucket extents. | ||
| 851 | const auto entry_count = header.entry_count; | ||
| 852 | const s64 data_offset = 0; | ||
| 853 | const s64 data_size = patch_info.aes_ctr_ex_offset; | ||
| 854 | const s64 node_offset = 0; | ||
| 855 | const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); | ||
| 856 | const s64 entry_offset = node_offset + node_size; | ||
| 857 | const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); | ||
| 858 | |||
| 859 | // Create bucket storages. | ||
| 860 | auto data_storage = | ||
| 861 | std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); | ||
| 862 | auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); | ||
| 863 | auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); | ||
| 864 | |||
| 865 | // Get the secure value. | ||
| 866 | const auto secure_value = upper_iv.part.secure_value; | ||
| 867 | |||
| 868 | // Create the aes ctr ex storage. | ||
| 869 | VirtualFile aes_ctr_ex_storage; | ||
| 870 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 871 | // Create the decryptor. | ||
| 872 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; | ||
| 873 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); | ||
| 874 | |||
| 875 | // Create the aes ctr ex storage. | ||
| 876 | auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 877 | R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 878 | |||
| 879 | // Initialize the aes ctr ex storage. | ||
| 880 | R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 881 | secure_value, counter_offset, data_storage, node_storage, | ||
| 882 | entry_storage, entry_count, std::move(decryptor))); | ||
| 883 | |||
| 884 | // Potentially set the output implementation storage. | ||
| 885 | if (out_ext != nullptr) { | ||
| 886 | *out_ext = impl_storage; | ||
| 887 | } | ||
| 888 | |||
| 889 | // Set the implementation storage. | ||
| 890 | aes_ctr_ex_storage = std::move(impl_storage); | ||
| 891 | } else { | ||
| 892 | // Create the software decryptor. | ||
| 893 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; | ||
| 894 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 895 | |||
| 896 | // Make the software storage. | ||
| 897 | auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 898 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 899 | |||
| 900 | // Initialize the software storage. | ||
| 901 | R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 902 | AesCtrStorage::KeySize, secure_value, counter_offset, | ||
| 903 | data_storage, node_storage, entry_storage, entry_count, | ||
| 904 | std::move(sw_decryptor))); | ||
| 905 | |||
| 906 | // Potentially set the output implementation storage. | ||
| 907 | if (out_ext != nullptr) { | ||
| 908 | *out_ext = sw_storage; | ||
| 909 | } | ||
| 910 | |||
| 911 | // Set the implementation storage. | ||
| 912 | aes_ctr_ex_storage = std::move(sw_storage); | ||
| 913 | } | ||
| 914 | |||
| 915 | // Create an alignment-matching storage. | ||
| 916 | using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; | ||
| 917 | auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); | ||
| 918 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 919 | |||
| 920 | // Set the output. | ||
| 921 | *out = std::move(aligned_storage); | ||
| 922 | R_SUCCEED(); | ||
| 923 | } | ||
| 924 | |||
| 925 | Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, | ||
| 926 | VirtualFile base_storage, | ||
| 927 | const NcaPatchInfo& patch_info) { | ||
| 928 | // Validate preconditions. | ||
| 929 | ASSERT(out != nullptr); | ||
| 930 | ASSERT(base_storage != nullptr); | ||
| 931 | ASSERT(patch_info.HasIndirectTable()); | ||
| 932 | |||
| 933 | // Get the base storage size. | ||
| 934 | s64 base_size = base_storage->GetSize(); | ||
| 935 | |||
| 936 | // Check that we're within range. | ||
| 937 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 938 | ResultNcaBaseStorageOutOfRangeE); | ||
| 939 | |||
| 940 | // Create the meta storage. | ||
| 941 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, | ||
| 942 | patch_info.indirect_offset); | ||
| 943 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 944 | |||
| 945 | // Create buffered storage. | ||
| 946 | std::vector<u8> meta_data(patch_info.indirect_size); | ||
| 947 | meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); | ||
| 948 | |||
| 949 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 950 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 951 | |||
| 952 | // Set the output. | ||
| 953 | *out = std::move(buffered_storage); | ||
| 954 | R_SUCCEED(); | ||
| 955 | } | ||
| 956 | |||
| 957 | Result NcaFileSystemDriver::CreateIndirectStorage( | ||
| 958 | VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, | ||
| 959 | VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { | ||
| 960 | // Validate preconditions. | ||
| 961 | ASSERT(out != nullptr); | ||
| 962 | ASSERT(base_storage != nullptr); | ||
| 963 | ASSERT(meta_storage != nullptr); | ||
| 964 | ASSERT(patch_info.HasIndirectTable()); | ||
| 965 | |||
| 966 | // Read the bucket tree header. | ||
| 967 | BucketTree::Header header; | ||
| 968 | std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); | ||
| 969 | R_TRY(header.Verify()); | ||
| 970 | |||
| 971 | // Determine the storage sizes. | ||
| 972 | const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); | ||
| 973 | const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); | ||
| 974 | R_UNLESS(node_size + entry_size <= patch_info.indirect_size, | ||
| 975 | ResultInvalidNcaIndirectStorageOutOfRange); | ||
| 976 | |||
| 977 | // Get the indirect data size. | ||
| 978 | const s64 indirect_data_size = patch_info.indirect_offset; | ||
| 979 | ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); | ||
| 980 | |||
| 981 | // Create the indirect data storage. | ||
| 982 | auto indirect_data_storage = | ||
| 983 | std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); | ||
| 984 | R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 985 | |||
| 986 | // Create the indirect storage. | ||
| 987 | auto indirect_storage = std::make_shared<IndirectStorage>(); | ||
| 988 | R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 989 | |||
| 990 | // Initialize the indirect storage. | ||
| 991 | R_TRY(indirect_storage->Initialize( | ||
| 992 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), | ||
| 993 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); | ||
| 994 | |||
| 995 | // Get the original data size. | ||
| 996 | s64 original_data_size = original_data_storage->GetSize(); | ||
| 997 | |||
| 998 | // Set the indirect storages. | ||
| 999 | indirect_storage->SetStorage( | ||
| 1000 | 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); | ||
| 1001 | indirect_storage->SetStorage( | ||
| 1002 | 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); | ||
| 1003 | |||
| 1004 | // If necessary, set the output indirect storage. | ||
| 1005 | if (out_ind != nullptr) { | ||
| 1006 | *out_ind = indirect_storage; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | // Set the output. | ||
| 1010 | *out = std::move(indirect_storage); | ||
| 1011 | R_SUCCEED(); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | Result NcaFileSystemDriver::CreatePatchMetaStorage( | ||
| 1015 | VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 1016 | VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1017 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, | ||
| 1018 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1019 | // Validate preconditions. | ||
| 1020 | ASSERT(out_aes_ctr_ex_meta != nullptr); | ||
| 1021 | ASSERT(out_indirect_meta != nullptr); | ||
| 1022 | ASSERT(base_storage != nullptr); | ||
| 1023 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 1024 | ASSERT(patch_info.HasIndirectTable()); | ||
| 1025 | ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); | ||
| 1026 | |||
| 1027 | // Validate patch info extents. | ||
| 1028 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 1029 | R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 1030 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 1031 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 1032 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= | ||
| 1033 | meta_data_hash_data_info.offset, | ||
| 1034 | ResultRomNcaInvalidPatchMetaDataHashDataOffset); | ||
| 1035 | |||
| 1036 | // Get the base storage size. | ||
| 1037 | s64 base_size = base_storage->GetSize(); | ||
| 1038 | |||
| 1039 | // Check that extents remain within range. | ||
| 1040 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 1041 | ResultNcaBaseStorageOutOfRangeE); | ||
| 1042 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, | ||
| 1043 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1044 | |||
| 1045 | // Check that metadata hash data extents remain within range. | ||
| 1046 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 1047 | const s64 meta_data_hash_data_size = | ||
| 1048 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 1049 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 1050 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1051 | |||
| 1052 | // Create the encrypted storage. | ||
| 1053 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 1054 | std::move(base_storage), | ||
| 1055 | meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, | ||
| 1056 | patch_info.indirect_offset); | ||
| 1057 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1058 | |||
| 1059 | // Create the decrypted storage. | ||
| 1060 | VirtualFile decrypted_storage; | ||
| 1061 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 1062 | offset + patch_info.indirect_offset, upper_iv, | ||
| 1063 | AlignmentStorageRequirement::None)); | ||
| 1064 | |||
| 1065 | // Create the verification storage. | ||
| 1066 | VirtualFile integrity_storage; | ||
| 1067 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 1068 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 1069 | patch_info.indirect_offset, meta_data_hash_data_info); | ||
| 1070 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 1071 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); | ||
| 1072 | } | ||
| 1073 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 1074 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); | ||
| 1075 | } | ||
| 1076 | R_TRY(rc); | ||
| 1077 | |||
| 1078 | // Create the indirect meta storage. | ||
| 1079 | auto indirect_meta_storage = | ||
| 1080 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, | ||
| 1081 | patch_info.indirect_offset - patch_info.indirect_offset); | ||
| 1082 | R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1083 | |||
| 1084 | // Create the aes ctr ex meta storage. | ||
| 1085 | auto aes_ctr_ex_meta_storage = | ||
| 1086 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, | ||
| 1087 | patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); | ||
| 1088 | R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1089 | |||
| 1090 | // Set the output. | ||
| 1091 | *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); | ||
| 1092 | *out_indirect_meta = std::move(indirect_meta_storage); | ||
| 1093 | R_SUCCEED(); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | Result NcaFileSystemDriver::CreateSha256Storage( | ||
| 1097 | VirtualFile* out, VirtualFile base_storage, | ||
| 1098 | const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { | ||
| 1099 | // Validate preconditions. | ||
| 1100 | ASSERT(out != nullptr); | ||
| 1101 | ASSERT(base_storage != nullptr); | ||
| 1102 | |||
| 1103 | // Define storage types. | ||
| 1104 | using VerificationStorage = HierarchicalSha256Storage; | ||
| 1105 | |||
| 1106 | // Validate the hash data. | ||
| 1107 | R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), | ||
| 1108 | ResultInvalidHierarchicalSha256BlockSize); | ||
| 1109 | R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, | ||
| 1110 | ResultInvalidHierarchicalSha256LayerCount); | ||
| 1111 | |||
| 1112 | // Get the regions. | ||
| 1113 | const auto& hash_region = hash_data.hash_layer_region[0]; | ||
| 1114 | const auto& data_region = hash_data.hash_layer_region[1]; | ||
| 1115 | |||
| 1116 | // Determine buffer sizes. | ||
| 1117 | constexpr s32 CacheBlockCount = 2; | ||
| 1118 | const auto hash_buffer_size = static_cast<size_t>(hash_region.size); | ||
| 1119 | const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; | ||
| 1120 | const auto total_buffer_size = hash_buffer_size + cache_buffer_size; | ||
| 1121 | |||
| 1122 | // Make a buffer holder storage. | ||
| 1123 | auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( | ||
| 1124 | std::move(base_storage), total_buffer_size); | ||
| 1125 | R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1126 | R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); | ||
| 1127 | |||
| 1128 | // Get storage size. | ||
| 1129 | s64 base_size = buffer_hold_storage->GetSize(); | ||
| 1130 | |||
| 1131 | // Check that we're within range. | ||
| 1132 | R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1133 | R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1134 | |||
| 1135 | // Create the master hash storage. | ||
| 1136 | auto master_hash_storage = | ||
| 1137 | std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); | ||
| 1138 | |||
| 1139 | // Make the verification storage. | ||
| 1140 | auto verification_storage = std::make_shared<VerificationStorage>(); | ||
| 1141 | R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1142 | |||
| 1143 | // Make layer storages. | ||
| 1144 | std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ | ||
| 1145 | std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), | ||
| 1146 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), | ||
| 1147 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), | ||
| 1148 | }; | ||
| 1149 | |||
| 1150 | // Initialize the verification storage. | ||
| 1151 | R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, | ||
| 1152 | hash_data.hash_block_size, | ||
| 1153 | buffer_hold_storage->GetBuffer(), hash_buffer_size)); | ||
| 1154 | |||
| 1155 | // Set the output. | ||
| 1156 | *out = std::move(verification_storage); | ||
| 1157 | R_SUCCEED(); | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( | ||
| 1161 | VirtualFile* out, VirtualFile base_storage, | ||
| 1162 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { | ||
| 1163 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1164 | out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, | ||
| 1165 | HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( | ||
| 1166 | meta_info.level_hash_info.max_layers))); | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( | ||
| 1170 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1171 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1172 | // Validate preconditions. | ||
| 1173 | ASSERT(out != nullptr); | ||
| 1174 | |||
| 1175 | // Check the meta data hash data size. | ||
| 1176 | R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), | ||
| 1177 | ResultInvalidNcaMetaDataHashDataSize); | ||
| 1178 | |||
| 1179 | // Read the meta data hash data. | ||
| 1180 | NcaMetaDataHashData meta_data_hash_data; | ||
| 1181 | base_storage->ReadObject(std::addressof(meta_data_hash_data), | ||
| 1182 | meta_data_hash_data_info.offset - offset); | ||
| 1183 | |||
| 1184 | // Set the out layer info storage, if necessary. | ||
| 1185 | if (out_layer_info_storage != nullptr) { | ||
| 1186 | auto layer_info_storage = std::make_shared<OffsetVfsFile>( | ||
| 1187 | base_storage, | ||
| 1188 | meta_data_hash_data_info.offset + meta_data_hash_data_info.size - | ||
| 1189 | meta_data_hash_data.layer_info_offset, | ||
| 1190 | meta_data_hash_data.layer_info_offset - offset); | ||
| 1191 | R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1192 | |||
| 1193 | *out_layer_info_storage = std::move(layer_info_storage); | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | // Create the meta storage. | ||
| 1197 | auto meta_storage = std::make_shared<OffsetVfsFile>( | ||
| 1198 | std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); | ||
| 1199 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1200 | |||
| 1201 | // Create the integrity verification storage. | ||
| 1202 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1203 | out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, | ||
| 1204 | meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, | ||
| 1205 | IntegrityHashCacheCountForMeta, 0)); | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( | ||
| 1209 | VirtualFile* out, VirtualFile base_storage, | ||
| 1210 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 1211 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 1212 | // Validate preconditions. | ||
| 1213 | ASSERT(out != nullptr); | ||
| 1214 | ASSERT(base_storage != nullptr); | ||
| 1215 | ASSERT(layer_info_offset >= 0); | ||
| 1216 | |||
| 1217 | // Define storage types. | ||
| 1218 | using VerificationStorage = HierarchicalIntegrityVerificationStorage; | ||
| 1219 | using StorageInfo = VerificationStorage::HierarchicalStorageInformation; | ||
| 1220 | |||
| 1221 | // Validate the meta info. | ||
| 1222 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 1223 | std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), | ||
| 1224 | sizeof(level_hash_info)); | ||
| 1225 | |||
| 1226 | R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, | ||
| 1227 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1228 | R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, | ||
| 1229 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1230 | |||
| 1231 | // Get the base storage size. | ||
| 1232 | s64 base_storage_size = base_storage->GetSize(); | ||
| 1233 | |||
| 1234 | // Create storage info. | ||
| 1235 | StorageInfo storage_info; | ||
| 1236 | for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { | ||
| 1237 | const auto& layer_info = level_hash_info.info[i]; | ||
| 1238 | R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, | ||
| 1239 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1240 | |||
| 1241 | storage_info[i + 1] = std::make_shared<OffsetVfsFile>( | ||
| 1242 | base_storage, layer_info.size, layer_info_offset + layer_info.offset); | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | // Set the last layer info. | ||
| 1246 | const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; | ||
| 1247 | const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); | ||
| 1248 | R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, | ||
| 1249 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1250 | if (layer_info_offset > 0) { | ||
| 1251 | R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, | ||
| 1252 | ResultRomNcaInvalidIntegrityLayerInfoOffset); | ||
| 1253 | } | ||
| 1254 | storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( | ||
| 1255 | std::move(base_storage), layer_info.size, last_layer_info_offset)); | ||
| 1256 | |||
| 1257 | // Make the integrity romfs storage. | ||
| 1258 | auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); | ||
| 1259 | R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1260 | |||
| 1261 | // Initialize the integrity storage. | ||
| 1262 | R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, | ||
| 1263 | max_data_cache_entries, max_hash_cache_entries, | ||
| 1264 | buffer_level)); | ||
| 1265 | |||
| 1266 | // Set the output. | ||
| 1267 | *out = std::move(integrity_storage); | ||
| 1268 | R_SUCCEED(); | ||
| 1269 | } | ||
| 1270 | |||
| 1271 | Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, | ||
| 1272 | const NcaFsHeaderReader* header_reader, | ||
| 1273 | VirtualFile inside_storage, | ||
| 1274 | VirtualFile outside_storage) { | ||
| 1275 | // Check pre-conditions. | ||
| 1276 | ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); | ||
| 1277 | |||
| 1278 | // Create the region. | ||
| 1279 | RegionSwitchStorage::Region region = {}; | ||
| 1280 | R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); | ||
| 1281 | |||
| 1282 | // Create the region switch storage. | ||
| 1283 | auto region_switch_storage = std::make_shared<RegionSwitchStorage>( | ||
| 1284 | std::move(inside_storage), std::move(outside_storage), region); | ||
| 1285 | R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1286 | |||
| 1287 | // Set the output. | ||
| 1288 | *out = std::move(region_switch_storage); | ||
| 1289 | R_SUCCEED(); | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1293 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1294 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1295 | const NcaCompressionInfo& compression_info) { | ||
| 1296 | R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), | ||
| 1297 | compression_info, m_reader->GetDecompressor())); | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1301 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1302 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1303 | const NcaCompressionInfo& compression_info, | ||
| 1304 | GetDecompressorFunction get_decompressor) { | ||
| 1305 | // Check pre-conditions. | ||
| 1306 | ASSERT(out != nullptr); | ||
| 1307 | ASSERT(base_storage != nullptr); | ||
| 1308 | ASSERT(get_decompressor != nullptr); | ||
| 1309 | |||
| 1310 | // Read and verify the bucket tree header. | ||
| 1311 | BucketTree::Header header; | ||
| 1312 | std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); | ||
| 1313 | R_TRY(header.Verify()); | ||
| 1314 | |||
| 1315 | // Determine the storage extents. | ||
| 1316 | const auto table_offset = compression_info.bucket.offset; | ||
| 1317 | const auto table_size = compression_info.bucket.size; | ||
| 1318 | const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); | ||
| 1319 | const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); | ||
| 1320 | R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); | ||
| 1321 | |||
| 1322 | // If we should, set the output meta storage. | ||
| 1323 | if (out_meta != nullptr) { | ||
| 1324 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); | ||
| 1325 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1326 | |||
| 1327 | *out_meta = std::move(meta_storage); | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | // Allocate the compressed storage. | ||
| 1331 | auto compressed_storage = std::make_shared<CompressedStorage>(); | ||
| 1332 | R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1333 | |||
| 1334 | // Initialize the compressed storage. | ||
| 1335 | R_TRY(compressed_storage->Initialize( | ||
| 1336 | std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), | ||
| 1337 | std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), | ||
| 1338 | std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), | ||
| 1339 | header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); | ||
| 1340 | |||
| 1341 | // Potentially set the output compressed storage. | ||
| 1342 | if (out_cmp) { | ||
| 1343 | *out_cmp = compressed_storage; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | // Set the output. | ||
| 1347 | *out = std::move(compressed_storage); | ||
| 1348 | R_SUCCEED(); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h new file mode 100644 index 000000000..5771a21fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h | |||
| @@ -0,0 +1,364 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class CompressedStorage; | ||
| 13 | class AesCtrCounterExtendedStorage; | ||
| 14 | class IndirectStorage; | ||
| 15 | class SparseStorage; | ||
| 16 | |||
| 17 | struct NcaCryptoConfiguration; | ||
| 18 | |||
| 19 | using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key, | ||
| 20 | size_t src_key_size, s32 key_type); | ||
| 21 | using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data, | ||
| 22 | size_t data_size, u8 generation); | ||
| 23 | |||
| 24 | struct NcaCryptoConfiguration { | ||
| 25 | static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8; | ||
| 26 | static constexpr size_t Rsa2048KeyPublicExponentSize = 3; | ||
| 27 | static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; | ||
| 28 | |||
| 29 | static constexpr size_t Aes128KeySize = 128 / 8; | ||
| 30 | |||
| 31 | static constexpr size_t Header1SignatureKeyGenerationMax = 1; | ||
| 32 | |||
| 33 | static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; | ||
| 34 | static constexpr s32 HeaderEncryptionKeyCount = 2; | ||
| 35 | |||
| 36 | static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; | ||
| 37 | |||
| 38 | static constexpr size_t KeyGenerationMax = 32; | ||
| 39 | |||
| 40 | std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli; | ||
| 41 | std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent; | ||
| 42 | std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount> | ||
| 43 | key_area_encryption_key_source; | ||
| 44 | std::array<u8, Aes128KeySize> header_encryption_key_source; | ||
| 45 | std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount> | ||
| 46 | header_encrypted_encryption_keys; | ||
| 47 | KeyGenerationFunction generate_key; | ||
| 48 | VerifySign1Function verify_sign1; | ||
| 49 | bool is_plaintext_header_available; | ||
| 50 | bool is_available_sw_key; | ||
| 51 | }; | ||
| 52 | static_assert(std::is_trivial_v<NcaCryptoConfiguration>); | ||
| 53 | |||
| 54 | struct NcaCompressionConfiguration { | ||
| 55 | GetDecompressorFunction get_decompressor; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<NcaCompressionConfiguration>); | ||
| 58 | |||
| 59 | constexpr inline s32 KeyAreaEncryptionKeyCount = | ||
| 60 | NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * | ||
| 61 | NcaCryptoConfiguration::KeyGenerationMax; | ||
| 62 | |||
| 63 | enum class KeyType : s32 { | ||
| 64 | ZeroKey = -2, | ||
| 65 | InvalidKey = -1, | ||
| 66 | NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, | ||
| 67 | NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, | ||
| 68 | NcaExternalKey = KeyAreaEncryptionKeyCount + 2, | ||
| 69 | SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, | ||
| 70 | SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, | ||
| 71 | SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, | ||
| 72 | }; | ||
| 73 | |||
| 74 | constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { | ||
| 75 | return key_type < 0; | ||
| 76 | } | ||
| 77 | |||
| 78 | constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { | ||
| 79 | if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { | ||
| 80 | return static_cast<s32>(KeyType::ZeroKey); | ||
| 81 | } | ||
| 82 | |||
| 83 | if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { | ||
| 84 | return static_cast<s32>(KeyType::InvalidKey); | ||
| 85 | } | ||
| 86 | |||
| 87 | return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; | ||
| 88 | } | ||
| 89 | |||
| 90 | class NcaReader { | ||
| 91 | YUZU_NON_COPYABLE(NcaReader); | ||
| 92 | YUZU_NON_MOVEABLE(NcaReader); | ||
| 93 | |||
| 94 | public: | ||
| 95 | NcaReader(); | ||
| 96 | ~NcaReader(); | ||
| 97 | |||
| 98 | Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 99 | const NcaCompressionConfiguration& compression_cfg); | ||
| 100 | |||
| 101 | VirtualFile GetSharedBodyStorage(); | ||
| 102 | u32 GetMagic() const; | ||
| 103 | NcaHeader::DistributionType GetDistributionType() const; | ||
| 104 | NcaHeader::ContentType GetContentType() const; | ||
| 105 | u8 GetHeaderSign1KeyGeneration() const; | ||
| 106 | u8 GetKeyGeneration() const; | ||
| 107 | u8 GetKeyIndex() const; | ||
| 108 | u64 GetContentSize() const; | ||
| 109 | u64 GetProgramId() const; | ||
| 110 | u32 GetContentIndex() const; | ||
| 111 | u32 GetSdkAddonVersion() const; | ||
| 112 | void GetRightsId(u8* dst, size_t dst_size) const; | ||
| 113 | bool HasFsInfo(s32 index) const; | ||
| 114 | s32 GetFsCount() const; | ||
| 115 | const Hash& GetFsHeaderHash(s32 index) const; | ||
| 116 | void GetFsHeaderHash(Hash* dst, s32 index) const; | ||
| 117 | void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const; | ||
| 118 | u64 GetFsOffset(s32 index) const; | ||
| 119 | u64 GetFsEndOffset(s32 index) const; | ||
| 120 | u64 GetFsSize(s32 index) const; | ||
| 121 | void GetEncryptedKey(void* dst, size_t size) const; | ||
| 122 | const void* GetDecryptionKey(s32 index) const; | ||
| 123 | bool HasValidInternalKey() const; | ||
| 124 | bool HasInternalDecryptionKeyForAesHw() const; | ||
| 125 | bool IsSoftwareAesPrioritized() const; | ||
| 126 | void PrioritizeSoftwareAes(); | ||
| 127 | bool IsAvailableSwKey() const; | ||
| 128 | bool HasExternalDecryptionKey() const; | ||
| 129 | const void* GetExternalDecryptionKey() const; | ||
| 130 | void SetExternalDecryptionKey(const void* src, size_t size); | ||
| 131 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 132 | NcaHeader::EncryptionType GetEncryptionType() const; | ||
| 133 | Result ReadHeader(NcaFsHeader* dst, s32 index) const; | ||
| 134 | |||
| 135 | GetDecompressorFunction GetDecompressor() const; | ||
| 136 | |||
| 137 | bool GetHeaderSign1Valid() const; | ||
| 138 | |||
| 139 | void GetHeaderSign2(void* dst, size_t size) const; | ||
| 140 | |||
| 141 | private: | ||
| 142 | NcaHeader m_header; | ||
| 143 | std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, | ||
| 144 | NcaHeader::DecryptionKey_Count> | ||
| 145 | m_decryption_keys; | ||
| 146 | VirtualFile m_body_storage; | ||
| 147 | VirtualFile m_header_storage; | ||
| 148 | std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key; | ||
| 149 | bool m_is_software_aes_prioritized; | ||
| 150 | bool m_is_available_sw_key; | ||
| 151 | NcaHeader::EncryptionType m_header_encryption_type; | ||
| 152 | bool m_is_header_sign1_signature_valid; | ||
| 153 | GetDecompressorFunction m_get_decompressor; | ||
| 154 | }; | ||
| 155 | |||
| 156 | class NcaFsHeaderReader { | ||
| 157 | YUZU_NON_COPYABLE(NcaFsHeaderReader); | ||
| 158 | YUZU_NON_MOVEABLE(NcaFsHeaderReader); | ||
| 159 | |||
| 160 | public: | ||
| 161 | NcaFsHeaderReader() : m_fs_index(-1) { | ||
| 162 | std::memset(std::addressof(m_data), 0, sizeof(m_data)); | ||
| 163 | } | ||
| 164 | |||
| 165 | Result Initialize(const NcaReader& reader, s32 index); | ||
| 166 | bool IsInitialized() const { | ||
| 167 | return m_fs_index >= 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 171 | |||
| 172 | NcaFsHeader::HashData& GetHashData(); | ||
| 173 | const NcaFsHeader::HashData& GetHashData() const; | ||
| 174 | u16 GetVersion() const; | ||
| 175 | s32 GetFsIndex() const; | ||
| 176 | NcaFsHeader::FsType GetFsType() const; | ||
| 177 | NcaFsHeader::HashType GetHashType() const; | ||
| 178 | NcaFsHeader::EncryptionType GetEncryptionType() const; | ||
| 179 | NcaPatchInfo& GetPatchInfo(); | ||
| 180 | const NcaPatchInfo& GetPatchInfo() const; | ||
| 181 | const NcaAesCtrUpperIv GetAesCtrUpperIv() const; | ||
| 182 | |||
| 183 | bool IsSkipLayerHashEncryption() const; | ||
| 184 | Result GetHashTargetOffset(s64* out) const; | ||
| 185 | |||
| 186 | bool ExistsSparseLayer() const; | ||
| 187 | NcaSparseInfo& GetSparseInfo(); | ||
| 188 | const NcaSparseInfo& GetSparseInfo() const; | ||
| 189 | |||
| 190 | bool ExistsCompressionLayer() const; | ||
| 191 | NcaCompressionInfo& GetCompressionInfo(); | ||
| 192 | const NcaCompressionInfo& GetCompressionInfo() const; | ||
| 193 | |||
| 194 | bool ExistsPatchMetaHashLayer() const; | ||
| 195 | NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo(); | ||
| 196 | const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const; | ||
| 197 | NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; | ||
| 198 | |||
| 199 | bool ExistsSparseMetaHashLayer() const; | ||
| 200 | NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo(); | ||
| 201 | const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const; | ||
| 202 | NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; | ||
| 203 | |||
| 204 | private: | ||
| 205 | NcaFsHeader m_data; | ||
| 206 | s32 m_fs_index; | ||
| 207 | }; | ||
| 208 | |||
| 209 | class NcaFileSystemDriver { | ||
| 210 | YUZU_NON_COPYABLE(NcaFileSystemDriver); | ||
| 211 | YUZU_NON_MOVEABLE(NcaFileSystemDriver); | ||
| 212 | |||
| 213 | public: | ||
| 214 | struct StorageContext { | ||
| 215 | bool open_raw_storage; | ||
| 216 | VirtualFile body_substorage; | ||
| 217 | std::shared_ptr<SparseStorage> current_sparse_storage; | ||
| 218 | VirtualFile sparse_storage_meta_storage; | ||
| 219 | std::shared_ptr<SparseStorage> original_sparse_storage; | ||
| 220 | void* external_current_sparse_storage; | ||
| 221 | void* external_original_sparse_storage; | ||
| 222 | VirtualFile aes_ctr_ex_storage_meta_storage; | ||
| 223 | VirtualFile aes_ctr_ex_storage_data_storage; | ||
| 224 | std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage; | ||
| 225 | VirtualFile indirect_storage_meta_storage; | ||
| 226 | std::shared_ptr<IndirectStorage> indirect_storage; | ||
| 227 | VirtualFile fs_data_storage; | ||
| 228 | VirtualFile compressed_storage_meta_storage; | ||
| 229 | std::shared_ptr<CompressedStorage> compressed_storage; | ||
| 230 | |||
| 231 | VirtualFile patch_layer_info_storage; | ||
| 232 | VirtualFile sparse_layer_info_storage; | ||
| 233 | |||
| 234 | VirtualFile external_original_storage; | ||
| 235 | }; | ||
| 236 | |||
| 237 | private: | ||
| 238 | enum class AlignmentStorageRequirement { | ||
| 239 | CacheBlockSize = 0, | ||
| 240 | None = 1, | ||
| 241 | }; | ||
| 242 | |||
| 243 | public: | ||
| 244 | static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader, | ||
| 245 | s32 fs_index); | ||
| 246 | |||
| 247 | public: | ||
| 248 | NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) { | ||
| 249 | ASSERT(m_reader != nullptr); | ||
| 250 | } | ||
| 251 | |||
| 252 | NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, | ||
| 253 | std::shared_ptr<NcaReader> reader) | ||
| 254 | : m_original_reader(original_reader), m_reader(reader) { | ||
| 255 | ASSERT(m_reader != nullptr); | ||
| 256 | } | ||
| 257 | |||
| 258 | Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 259 | s32 fs_index, StorageContext* ctx); | ||
| 260 | |||
| 261 | Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) { | ||
| 262 | // Create a storage context. | ||
| 263 | StorageContext ctx{}; | ||
| 264 | |||
| 265 | // Open the storage. | ||
| 266 | R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx))); | ||
| 267 | } | ||
| 268 | |||
| 269 | public: | ||
| 270 | Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 271 | VirtualFile raw_storage, StorageContext* ctx); | ||
| 272 | |||
| 273 | private: | ||
| 274 | Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index, | ||
| 275 | StorageContext* ctx); | ||
| 276 | |||
| 277 | Result OpenIndirectableStorageAsOriginal(VirtualFile* out, | ||
| 278 | const NcaFsHeaderReader* header_reader, | ||
| 279 | StorageContext* ctx); | ||
| 280 | |||
| 281 | Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size); | ||
| 282 | |||
| 283 | Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 284 | const NcaAesCtrUpperIv& upper_iv, | ||
| 285 | AlignmentStorageRequirement alignment_storage_requirement); | ||
| 286 | Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset); | ||
| 287 | |||
| 288 | Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 289 | const NcaAesCtrUpperIv& upper_iv, | ||
| 290 | const NcaSparseInfo& sparse_info); | ||
| 291 | Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage, | ||
| 292 | s64 base_size, VirtualFile meta_storage, | ||
| 293 | const NcaSparseInfo& sparse_info, bool external_info); | ||
| 294 | Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 295 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 296 | VirtualFile* out_meta_storage, s32 index, | ||
| 297 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info); | ||
| 298 | |||
| 299 | Result CreateSparseStorageMetaStorageWithVerification( | ||
| 300 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 301 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 302 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 303 | Result CreateSparseStorageWithVerification( | ||
| 304 | VirtualFile* out, s64* out_fs_data_offset, | ||
| 305 | std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage, | ||
| 306 | VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv, | ||
| 307 | const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 308 | NcaFsHeader::MetaDataHashType meta_data_hash_type); | ||
| 309 | |||
| 310 | Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 311 | NcaFsHeader::EncryptionType encryption_type, | ||
| 312 | const NcaAesCtrUpperIv& upper_iv, | ||
| 313 | const NcaPatchInfo& patch_info); | ||
| 314 | Result CreateAesCtrExStorage(VirtualFile* out, | ||
| 315 | std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 316 | VirtualFile base_storage, VirtualFile meta_storage, | ||
| 317 | s64 counter_offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 318 | const NcaPatchInfo& patch_info); | ||
| 319 | |||
| 320 | Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 321 | const NcaPatchInfo& patch_info); | ||
| 322 | Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, | ||
| 323 | VirtualFile base_storage, VirtualFile original_data_storage, | ||
| 324 | VirtualFile meta_storage, const NcaPatchInfo& patch_info); | ||
| 325 | |||
| 326 | Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 327 | VirtualFile* out_verification, VirtualFile base_storage, | ||
| 328 | s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 329 | const NcaPatchInfo& patch_info, | ||
| 330 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 331 | |||
| 332 | Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, | ||
| 333 | const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); | ||
| 334 | |||
| 335 | Result CreateIntegrityVerificationStorage( | ||
| 336 | VirtualFile* out, VirtualFile base_storage, | ||
| 337 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info); | ||
| 338 | Result CreateIntegrityVerificationStorageForMeta( | ||
| 339 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 340 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 341 | Result CreateIntegrityVerificationStorageImpl( | ||
| 342 | VirtualFile* out, VirtualFile base_storage, | ||
| 343 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 344 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 345 | |||
| 346 | Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 347 | VirtualFile inside_storage, VirtualFile outside_storage); | ||
| 348 | |||
| 349 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 350 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 351 | const NcaCompressionInfo& compression_info); | ||
| 352 | |||
| 353 | public: | ||
| 354 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 355 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 356 | const NcaCompressionInfo& compression_info, | ||
| 357 | GetDecompressorFunction get_decompressor); | ||
| 358 | |||
| 359 | private: | ||
| 360 | std::shared_ptr<NcaReader> m_original_reader; | ||
| 361 | std::shared_ptr<NcaReader> m_reader; | ||
| 362 | }; | ||
| 363 | |||
| 364 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp new file mode 100644 index 000000000..bf5742d39 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | u8 NcaHeader::GetProperKeyGeneration() const { | ||
| 9 | return std::max(this->key_generation, this->key_generation_2); | ||
| 10 | } | ||
| 11 | |||
| 12 | bool NcaPatchInfo::HasIndirectTable() const { | ||
| 13 | return this->indirect_size != 0; | ||
| 14 | } | ||
| 15 | |||
| 16 | bool NcaPatchInfo::HasAesCtrExTable() const { | ||
| 17 | return this->aes_ctr_ex_size != 0; | ||
| 18 | } | ||
| 19 | |||
| 20 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h new file mode 100644 index 000000000..a02c5d881 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h | |||
| @@ -0,0 +1,338 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | |||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | using namespace Common::Literals; | ||
| 16 | |||
| 17 | struct Hash { | ||
| 18 | static constexpr std::size_t Size = 256 / 8; | ||
| 19 | std::array<u8, Size> value; | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(Hash) == Hash::Size); | ||
| 22 | static_assert(std::is_trivial_v<Hash>); | ||
| 23 | |||
| 24 | using NcaDigest = Hash; | ||
| 25 | |||
| 26 | struct NcaHeader { | ||
| 27 | enum class ContentType : u8 { | ||
| 28 | Program = 0, | ||
| 29 | Meta = 1, | ||
| 30 | Control = 2, | ||
| 31 | Manual = 3, | ||
| 32 | Data = 4, | ||
| 33 | PublicData = 5, | ||
| 34 | |||
| 35 | Start = Program, | ||
| 36 | End = PublicData, | ||
| 37 | }; | ||
| 38 | |||
| 39 | enum class DistributionType : u8 { | ||
| 40 | Download = 0, | ||
| 41 | GameCard = 1, | ||
| 42 | |||
| 43 | Start = Download, | ||
| 44 | End = GameCard, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class EncryptionType : u8 { | ||
| 48 | Auto = 0, | ||
| 49 | None = 1, | ||
| 50 | }; | ||
| 51 | |||
| 52 | enum DecryptionKey { | ||
| 53 | DecryptionKey_AesXts = 0, | ||
| 54 | DecryptionKey_AesXts1 = DecryptionKey_AesXts, | ||
| 55 | DecryptionKey_AesXts2 = 1, | ||
| 56 | DecryptionKey_AesCtr = 2, | ||
| 57 | DecryptionKey_AesCtrEx = 3, | ||
| 58 | DecryptionKey_AesCtrHw = 4, | ||
| 59 | DecryptionKey_Count, | ||
| 60 | }; | ||
| 61 | |||
| 62 | struct FsInfo { | ||
| 63 | u32 start_sector; | ||
| 64 | u32 end_sector; | ||
| 65 | u32 hash_sectors; | ||
| 66 | u32 reserved; | ||
| 67 | }; | ||
| 68 | static_assert(sizeof(FsInfo) == 0x10); | ||
| 69 | static_assert(std::is_trivial_v<FsInfo>); | ||
| 70 | |||
| 71 | static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); | ||
| 72 | static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); | ||
| 73 | static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); | ||
| 74 | static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 75 | |||
| 76 | static constexpr u32 Magic = Magic3; | ||
| 77 | |||
| 78 | static constexpr std::size_t Size = 1_KiB; | ||
| 79 | static constexpr s32 FsCountMax = 4; | ||
| 80 | static constexpr std::size_t HeaderSignCount = 2; | ||
| 81 | static constexpr std::size_t HeaderSignSize = 0x100; | ||
| 82 | static constexpr std::size_t EncryptedKeyAreaSize = 0x100; | ||
| 83 | static constexpr std::size_t SectorSize = 0x200; | ||
| 84 | static constexpr std::size_t SectorShift = 9; | ||
| 85 | static constexpr std::size_t RightsIdSize = 0x10; | ||
| 86 | static constexpr std::size_t XtsBlockSize = 0x200; | ||
| 87 | static constexpr std::size_t CtrBlockSize = 0x10; | ||
| 88 | |||
| 89 | static_assert(SectorSize == (1 << SectorShift)); | ||
| 90 | |||
| 91 | // Data members. | ||
| 92 | std::array<u8, HeaderSignSize> header_sign_1; | ||
| 93 | std::array<u8, HeaderSignSize> header_sign_2; | ||
| 94 | u32 magic; | ||
| 95 | DistributionType distribution_type; | ||
| 96 | ContentType content_type; | ||
| 97 | u8 key_generation; | ||
| 98 | u8 key_index; | ||
| 99 | u64 content_size; | ||
| 100 | u64 program_id; | ||
| 101 | u32 content_index; | ||
| 102 | u32 sdk_addon_version; | ||
| 103 | u8 key_generation_2; | ||
| 104 | u8 header1_signature_key_generation; | ||
| 105 | std::array<u8, 2> reserved_222; | ||
| 106 | std::array<u32, 3> reserved_224; | ||
| 107 | std::array<u8, RightsIdSize> rights_id; | ||
| 108 | std::array<FsInfo, FsCountMax> fs_info; | ||
| 109 | std::array<Hash, FsCountMax> fs_header_hash; | ||
| 110 | std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; | ||
| 111 | |||
| 112 | static constexpr u64 SectorToByte(u32 sector) { | ||
| 113 | return static_cast<u64>(sector) << SectorShift; | ||
| 114 | } | ||
| 115 | |||
| 116 | static constexpr u32 ByteToSector(u64 byte) { | ||
| 117 | return static_cast<u32>(byte >> SectorShift); | ||
| 118 | } | ||
| 119 | |||
| 120 | u8 GetProperKeyGeneration() const; | ||
| 121 | }; | ||
| 122 | static_assert(sizeof(NcaHeader) == NcaHeader::Size); | ||
| 123 | static_assert(std::is_trivial_v<NcaHeader>); | ||
| 124 | |||
| 125 | struct NcaBucketInfo { | ||
| 126 | static constexpr size_t HeaderSize = 0x10; | ||
| 127 | Int64 offset; | ||
| 128 | Int64 size; | ||
| 129 | std::array<u8, HeaderSize> header; | ||
| 130 | }; | ||
| 131 | static_assert(std::is_trivial_v<NcaBucketInfo>); | ||
| 132 | |||
| 133 | struct NcaPatchInfo { | ||
| 134 | static constexpr size_t Size = 0x40; | ||
| 135 | static constexpr size_t Offset = 0x100; | ||
| 136 | |||
| 137 | Int64 indirect_offset; | ||
| 138 | Int64 indirect_size; | ||
| 139 | std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; | ||
| 140 | Int64 aes_ctr_ex_offset; | ||
| 141 | Int64 aes_ctr_ex_size; | ||
| 142 | std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; | ||
| 143 | |||
| 144 | bool HasIndirectTable() const; | ||
| 145 | bool HasAesCtrExTable() const; | ||
| 146 | }; | ||
| 147 | static_assert(std::is_trivial_v<NcaPatchInfo>); | ||
| 148 | |||
| 149 | union NcaAesCtrUpperIv { | ||
| 150 | u64 value; | ||
| 151 | struct { | ||
| 152 | u32 generation; | ||
| 153 | u32 secure_value; | ||
| 154 | } part; | ||
| 155 | }; | ||
| 156 | static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); | ||
| 157 | |||
| 158 | struct NcaSparseInfo { | ||
| 159 | NcaBucketInfo bucket; | ||
| 160 | Int64 physical_offset; | ||
| 161 | u16 generation; | ||
| 162 | std::array<u8, 6> reserved; | ||
| 163 | |||
| 164 | s64 GetPhysicalSize() const { | ||
| 165 | return this->bucket.offset + this->bucket.size; | ||
| 166 | } | ||
| 167 | |||
| 168 | u32 GetGeneration() const { | ||
| 169 | return static_cast<u32>(this->generation) << 16; | ||
| 170 | } | ||
| 171 | |||
| 172 | const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { | ||
| 173 | NcaAesCtrUpperIv sparse_upper_iv = upper_iv; | ||
| 174 | sparse_upper_iv.part.generation = this->GetGeneration(); | ||
| 175 | return sparse_upper_iv; | ||
| 176 | } | ||
| 177 | }; | ||
| 178 | static_assert(std::is_trivial_v<NcaSparseInfo>); | ||
| 179 | |||
| 180 | struct NcaCompressionInfo { | ||
| 181 | NcaBucketInfo bucket; | ||
| 182 | std::array<u8, 8> resreved; | ||
| 183 | }; | ||
| 184 | static_assert(std::is_trivial_v<NcaCompressionInfo>); | ||
| 185 | |||
| 186 | struct NcaMetaDataHashDataInfo { | ||
| 187 | Int64 offset; | ||
| 188 | Int64 size; | ||
| 189 | Hash hash; | ||
| 190 | }; | ||
| 191 | static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); | ||
| 192 | |||
| 193 | struct NcaFsHeader { | ||
| 194 | static constexpr size_t Size = 0x200; | ||
| 195 | static constexpr size_t HashDataOffset = 0x8; | ||
| 196 | |||
| 197 | struct Region { | ||
| 198 | Int64 offset; | ||
| 199 | Int64 size; | ||
| 200 | }; | ||
| 201 | static_assert(std::is_trivial_v<Region>); | ||
| 202 | |||
| 203 | enum class FsType : u8 { | ||
| 204 | RomFs = 0, | ||
| 205 | PartitionFs = 1, | ||
| 206 | }; | ||
| 207 | |||
| 208 | enum class EncryptionType : u8 { | ||
| 209 | Auto = 0, | ||
| 210 | None = 1, | ||
| 211 | AesXts = 2, | ||
| 212 | AesCtr = 3, | ||
| 213 | AesCtrEx = 4, | ||
| 214 | AesCtrSkipLayerHash = 5, | ||
| 215 | AesCtrExSkipLayerHash = 6, | ||
| 216 | }; | ||
| 217 | |||
| 218 | enum class HashType : u8 { | ||
| 219 | Auto = 0, | ||
| 220 | None = 1, | ||
| 221 | HierarchicalSha256Hash = 2, | ||
| 222 | HierarchicalIntegrityHash = 3, | ||
| 223 | AutoSha3 = 4, | ||
| 224 | HierarchicalSha3256Hash = 5, | ||
| 225 | HierarchicalIntegritySha3Hash = 6, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class MetaDataHashType : u8 { | ||
| 229 | None = 0, | ||
| 230 | HierarchicalIntegrity = 1, | ||
| 231 | }; | ||
| 232 | |||
| 233 | union HashData { | ||
| 234 | struct HierarchicalSha256Data { | ||
| 235 | static constexpr size_t HashLayerCountMax = 5; | ||
| 236 | static const size_t MasterHashOffset; | ||
| 237 | |||
| 238 | Hash fs_data_master_hash; | ||
| 239 | s32 hash_block_size; | ||
| 240 | s32 hash_layer_count; | ||
| 241 | std::array<Region, HashLayerCountMax> hash_layer_region; | ||
| 242 | } hierarchical_sha256_data; | ||
| 243 | static_assert(std::is_trivial_v<HierarchicalSha256Data>); | ||
| 244 | |||
| 245 | struct IntegrityMetaInfo { | ||
| 246 | static const size_t MasterHashOffset; | ||
| 247 | |||
| 248 | u32 magic; | ||
| 249 | u32 version; | ||
| 250 | u32 master_hash_size; | ||
| 251 | |||
| 252 | struct LevelHashInfo { | ||
| 253 | u32 max_layers; | ||
| 254 | |||
| 255 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 256 | static constexpr size_t IntegrityMaxLayerCount = 7; | ||
| 257 | Int64 offset; | ||
| 258 | Int64 size; | ||
| 259 | s32 block_order; | ||
| 260 | std::array<u8, 4> reserved; | ||
| 261 | }; | ||
| 262 | std::array< | ||
| 263 | HierarchicalIntegrityVerificationLevelInformation, | ||
| 264 | HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> | ||
| 265 | info; | ||
| 266 | |||
| 267 | struct SignatureSalt { | ||
| 268 | static constexpr size_t Size = 0x20; | ||
| 269 | std::array<u8, Size> value; | ||
| 270 | }; | ||
| 271 | SignatureSalt seed; | ||
| 272 | } level_hash_info; | ||
| 273 | |||
| 274 | Hash master_hash; | ||
| 275 | } integrity_meta_info; | ||
| 276 | static_assert(std::is_trivial_v<IntegrityMetaInfo>); | ||
| 277 | |||
| 278 | std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; | ||
| 279 | }; | ||
| 280 | |||
| 281 | u16 version; | ||
| 282 | FsType fs_type; | ||
| 283 | HashType hash_type; | ||
| 284 | EncryptionType encryption_type; | ||
| 285 | MetaDataHashType meta_data_hash_type; | ||
| 286 | std::array<u8, 2> reserved; | ||
| 287 | HashData hash_data; | ||
| 288 | NcaPatchInfo patch_info; | ||
| 289 | NcaAesCtrUpperIv aes_ctr_upper_iv; | ||
| 290 | NcaSparseInfo sparse_info; | ||
| 291 | NcaCompressionInfo compression_info; | ||
| 292 | NcaMetaDataHashDataInfo meta_data_hash_data_info; | ||
| 293 | std::array<u8, 0x30> pad; | ||
| 294 | |||
| 295 | bool IsSkipLayerHashEncryption() const { | ||
| 296 | return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || | ||
| 297 | this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; | ||
| 298 | } | ||
| 299 | |||
| 300 | Result GetHashTargetOffset(s64* out) const { | ||
| 301 | switch (this->hash_type) { | ||
| 302 | case HashType::HierarchicalIntegrityHash: | ||
| 303 | case HashType::HierarchicalIntegritySha3Hash: | ||
| 304 | *out = this->hash_data.integrity_meta_info.level_hash_info | ||
| 305 | .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] | ||
| 306 | .offset; | ||
| 307 | R_SUCCEED(); | ||
| 308 | case HashType::HierarchicalSha256Hash: | ||
| 309 | case HashType::HierarchicalSha3256Hash: | ||
| 310 | *out = | ||
| 311 | this->hash_data.hierarchical_sha256_data | ||
| 312 | .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - | ||
| 313 | 1] | ||
| 314 | .offset; | ||
| 315 | R_SUCCEED(); | ||
| 316 | default: | ||
| 317 | R_THROW(ResultInvalidNcaFsHeader); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | }; | ||
| 321 | static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); | ||
| 322 | static_assert(std::is_trivial_v<NcaFsHeader>); | ||
| 323 | static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); | ||
| 324 | |||
| 325 | inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = | ||
| 326 | offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); | ||
| 327 | inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = | ||
| 328 | offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); | ||
| 329 | |||
| 330 | struct NcaMetaDataHashData { | ||
| 331 | s64 layer_info_offset; | ||
| 332 | NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; | ||
| 333 | }; | ||
| 334 | static_assert(sizeof(NcaMetaDataHashData) == | ||
| 335 | sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); | ||
| 336 | static_assert(std::is_trivial_v<NcaMetaDataHashData>); | ||
| 337 | |||
| 338 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..a3714ab37 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp | |||
| @@ -0,0 +1,531 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 6 | #include "core/file_sys/vfs_offset.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | constexpr inline u32 SdkAddonVersionMin = 0x000B0000; | ||
| 13 | constexpr inline size_t Aes128KeySize = 0x10; | ||
| 14 | constexpr const std::array<u8, Aes128KeySize> ZeroKey{}; | ||
| 15 | |||
| 16 | constexpr Result CheckNcaMagic(u32 magic) { | ||
| 17 | // Verify the magic is not a deprecated one. | ||
| 18 | R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); | ||
| 19 | R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); | ||
| 20 | R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); | ||
| 21 | |||
| 22 | // Verify the magic is the current one. | ||
| 23 | R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); | ||
| 24 | |||
| 25 | R_SUCCEED(); | ||
| 26 | } | ||
| 27 | |||
| 28 | } // namespace | ||
| 29 | |||
| 30 | NcaReader::NcaReader() | ||
| 31 | : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), | ||
| 32 | m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), | ||
| 33 | m_get_decompressor() { | ||
| 34 | std::memset(std::addressof(m_header), 0, sizeof(m_header)); | ||
| 35 | std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); | ||
| 36 | std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); | ||
| 37 | } | ||
| 38 | |||
| 39 | NcaReader::~NcaReader() {} | ||
| 40 | |||
| 41 | Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 42 | const NcaCompressionConfiguration& compression_cfg) { | ||
| 43 | // Validate preconditions. | ||
| 44 | ASSERT(base_storage != nullptr); | ||
| 45 | ASSERT(m_body_storage == nullptr); | ||
| 46 | |||
| 47 | // Create the work header storage storage. | ||
| 48 | VirtualFile work_header_storage; | ||
| 49 | |||
| 50 | // We need to be able to generate keys. | ||
| 51 | R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); | ||
| 52 | |||
| 53 | // Generate keys for header. | ||
| 54 | using AesXtsStorageForNcaHeader = AesXtsStorage; | ||
| 55 | |||
| 56 | constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount> | ||
| 57 | HeaderKeyTypeValues = { | ||
| 58 | static_cast<s32>(KeyType::NcaHeaderKey1), | ||
| 59 | static_cast<s32>(KeyType::NcaHeaderKey2), | ||
| 60 | }; | ||
| 61 | |||
| 62 | std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, | ||
| 63 | NcaCryptoConfiguration::HeaderEncryptionKeyCount> | ||
| 64 | header_decryption_keys; | ||
| 65 | for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { | ||
| 66 | crypto_cfg.generate_key(header_decryption_keys[i].data(), | ||
| 67 | AesXtsStorageForNcaHeader::KeySize, | ||
| 68 | crypto_cfg.header_encrypted_encryption_keys[i].data(), | ||
| 69 | AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); | ||
| 70 | } | ||
| 71 | |||
| 72 | // Create the header storage. | ||
| 73 | std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {}; | ||
| 74 | work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>( | ||
| 75 | base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(), | ||
| 76 | AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize, | ||
| 77 | NcaHeader::XtsBlockSize); | ||
| 78 | |||
| 79 | // Check that we successfully created the storage. | ||
| 80 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 81 | |||
| 82 | // Read the header. | ||
| 83 | work_header_storage->ReadObject(std::addressof(m_header), 0); | ||
| 84 | |||
| 85 | // Validate the magic. | ||
| 86 | if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { | ||
| 87 | // Try to use a plaintext header. | ||
| 88 | base_storage->ReadObject(std::addressof(m_header), 0); | ||
| 89 | R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); | ||
| 90 | |||
| 91 | // Configure to use the plaintext header. | ||
| 92 | auto base_storage_size = base_storage->GetSize(); | ||
| 93 | work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0); | ||
| 94 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 95 | |||
| 96 | // Set encryption type as plaintext. | ||
| 97 | m_header_encryption_type = NcaHeader::EncryptionType::None; | ||
| 98 | } | ||
| 99 | |||
| 100 | // Verify the header sign1. | ||
| 101 | if (crypto_cfg.verify_sign1 != nullptr) { | ||
| 102 | const u8* sig = m_header.header_sign_1.data(); | ||
| 103 | const size_t sig_size = NcaHeader::HeaderSignSize; | ||
| 104 | const u8* msg = | ||
| 105 | static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic))); | ||
| 106 | const size_t msg_size = | ||
| 107 | NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; | ||
| 108 | |||
| 109 | m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( | ||
| 110 | sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); | ||
| 111 | |||
| 112 | if (!m_is_header_sign1_signature_valid) { | ||
| 113 | LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | // Validate the sdk version. | ||
| 118 | R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); | ||
| 119 | |||
| 120 | // Validate the key index. | ||
| 121 | R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || | ||
| 122 | m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, | ||
| 123 | ResultInvalidNcaKeyIndex); | ||
| 124 | |||
| 125 | // Check if we have a rights id. | ||
| 126 | constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{}; | ||
| 127 | if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { | ||
| 128 | // If we don't, then we don't have an external key, so we need to generate decryption keys. | ||
| 129 | crypto_cfg.generate_key( | ||
| 130 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize, | ||
| 131 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, | ||
| 132 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 133 | crypto_cfg.generate_key( | ||
| 134 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize, | ||
| 135 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, | ||
| 136 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 137 | crypto_cfg.generate_key( | ||
| 138 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize, | ||
| 139 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, | ||
| 140 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 141 | crypto_cfg.generate_key( | ||
| 142 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize, | ||
| 143 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, | ||
| 144 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 145 | |||
| 146 | // Copy the hardware speed emulation key. | ||
| 147 | std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(), | ||
| 148 | m_header.encrypted_key_area.data() + | ||
| 149 | NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, | ||
| 150 | Aes128KeySize); | ||
| 151 | } | ||
| 152 | |||
| 153 | // Clear the external decryption key. | ||
| 154 | std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size()); | ||
| 155 | |||
| 156 | // Set software key availability. | ||
| 157 | m_is_available_sw_key = crypto_cfg.is_available_sw_key; | ||
| 158 | |||
| 159 | // Set our decompressor function getter. | ||
| 160 | m_get_decompressor = compression_cfg.get_decompressor; | ||
| 161 | |||
| 162 | // Set our storages. | ||
| 163 | m_header_storage = std::move(work_header_storage); | ||
| 164 | m_body_storage = std::move(base_storage); | ||
| 165 | |||
| 166 | R_SUCCEED(); | ||
| 167 | } | ||
| 168 | |||
| 169 | VirtualFile NcaReader::GetSharedBodyStorage() { | ||
| 170 | ASSERT(m_body_storage != nullptr); | ||
| 171 | return m_body_storage; | ||
| 172 | } | ||
| 173 | |||
| 174 | u32 NcaReader::GetMagic() const { | ||
| 175 | ASSERT(m_body_storage != nullptr); | ||
| 176 | return m_header.magic; | ||
| 177 | } | ||
| 178 | |||
| 179 | NcaHeader::DistributionType NcaReader::GetDistributionType() const { | ||
| 180 | ASSERT(m_body_storage != nullptr); | ||
| 181 | return m_header.distribution_type; | ||
| 182 | } | ||
| 183 | |||
| 184 | NcaHeader::ContentType NcaReader::GetContentType() const { | ||
| 185 | ASSERT(m_body_storage != nullptr); | ||
| 186 | return m_header.content_type; | ||
| 187 | } | ||
| 188 | |||
| 189 | u8 NcaReader::GetHeaderSign1KeyGeneration() const { | ||
| 190 | ASSERT(m_body_storage != nullptr); | ||
| 191 | return m_header.header1_signature_key_generation; | ||
| 192 | } | ||
| 193 | |||
| 194 | u8 NcaReader::GetKeyGeneration() const { | ||
| 195 | ASSERT(m_body_storage != nullptr); | ||
| 196 | return m_header.GetProperKeyGeneration(); | ||
| 197 | } | ||
| 198 | |||
| 199 | u8 NcaReader::GetKeyIndex() const { | ||
| 200 | ASSERT(m_body_storage != nullptr); | ||
| 201 | return m_header.key_index; | ||
| 202 | } | ||
| 203 | |||
| 204 | u64 NcaReader::GetContentSize() const { | ||
| 205 | ASSERT(m_body_storage != nullptr); | ||
| 206 | return m_header.content_size; | ||
| 207 | } | ||
| 208 | |||
| 209 | u64 NcaReader::GetProgramId() const { | ||
| 210 | ASSERT(m_body_storage != nullptr); | ||
| 211 | return m_header.program_id; | ||
| 212 | } | ||
| 213 | |||
| 214 | u32 NcaReader::GetContentIndex() const { | ||
| 215 | ASSERT(m_body_storage != nullptr); | ||
| 216 | return m_header.content_index; | ||
| 217 | } | ||
| 218 | |||
| 219 | u32 NcaReader::GetSdkAddonVersion() const { | ||
| 220 | ASSERT(m_body_storage != nullptr); | ||
| 221 | return m_header.sdk_addon_version; | ||
| 222 | } | ||
| 223 | |||
| 224 | void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { | ||
| 225 | ASSERT(dst != nullptr); | ||
| 226 | ASSERT(dst_size >= NcaHeader::RightsIdSize); | ||
| 227 | |||
| 228 | std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | bool NcaReader::HasFsInfo(s32 index) const { | ||
| 232 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 233 | return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; | ||
| 234 | } | ||
| 235 | |||
| 236 | s32 NcaReader::GetFsCount() const { | ||
| 237 | ASSERT(m_body_storage != nullptr); | ||
| 238 | for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { | ||
| 239 | if (!this->HasFsInfo(i)) { | ||
| 240 | return i; | ||
| 241 | } | ||
| 242 | } | ||
| 243 | return NcaHeader::FsCountMax; | ||
| 244 | } | ||
| 245 | |||
| 246 | const Hash& NcaReader::GetFsHeaderHash(s32 index) const { | ||
| 247 | ASSERT(m_body_storage != nullptr); | ||
| 248 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 249 | return m_header.fs_header_hash[index]; | ||
| 250 | } | ||
| 251 | |||
| 252 | void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { | ||
| 253 | ASSERT(m_body_storage != nullptr); | ||
| 254 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 255 | ASSERT(dst != nullptr); | ||
| 256 | std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); | ||
| 257 | } | ||
| 258 | |||
| 259 | void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { | ||
| 260 | ASSERT(m_body_storage != nullptr); | ||
| 261 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 262 | ASSERT(dst != nullptr); | ||
| 263 | std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); | ||
| 264 | } | ||
| 265 | |||
| 266 | u64 NcaReader::GetFsOffset(s32 index) const { | ||
| 267 | ASSERT(m_body_storage != nullptr); | ||
| 268 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 269 | return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); | ||
| 270 | } | ||
| 271 | |||
| 272 | u64 NcaReader::GetFsEndOffset(s32 index) const { | ||
| 273 | ASSERT(m_body_storage != nullptr); | ||
| 274 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 275 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); | ||
| 276 | } | ||
| 277 | |||
| 278 | u64 NcaReader::GetFsSize(s32 index) const { | ||
| 279 | ASSERT(m_body_storage != nullptr); | ||
| 280 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 281 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - | ||
| 282 | m_header.fs_info[index].start_sector); | ||
| 283 | } | ||
| 284 | |||
| 285 | void NcaReader::GetEncryptedKey(void* dst, size_t size) const { | ||
| 286 | ASSERT(m_body_storage != nullptr); | ||
| 287 | ASSERT(dst != nullptr); | ||
| 288 | ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); | ||
| 289 | |||
| 290 | std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); | ||
| 291 | } | ||
| 292 | |||
| 293 | const void* NcaReader::GetDecryptionKey(s32 index) const { | ||
| 294 | ASSERT(m_body_storage != nullptr); | ||
| 295 | ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); | ||
| 296 | return m_decryption_keys[index].data(); | ||
| 297 | } | ||
| 298 | |||
| 299 | bool NcaReader::HasValidInternalKey() const { | ||
| 300 | for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { | ||
| 301 | if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, | ||
| 302 | Aes128KeySize) != 0) { | ||
| 303 | return true; | ||
| 304 | } | ||
| 305 | } | ||
| 306 | return false; | ||
| 307 | } | ||
| 308 | |||
| 309 | bool NcaReader::HasInternalDecryptionKeyForAesHw() const { | ||
| 310 | return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), | ||
| 311 | Aes128KeySize) != 0; | ||
| 312 | } | ||
| 313 | |||
| 314 | bool NcaReader::IsSoftwareAesPrioritized() const { | ||
| 315 | return m_is_software_aes_prioritized; | ||
| 316 | } | ||
| 317 | |||
| 318 | void NcaReader::PrioritizeSoftwareAes() { | ||
| 319 | m_is_software_aes_prioritized = true; | ||
| 320 | } | ||
| 321 | |||
| 322 | bool NcaReader::IsAvailableSwKey() const { | ||
| 323 | return m_is_available_sw_key; | ||
| 324 | } | ||
| 325 | |||
| 326 | bool NcaReader::HasExternalDecryptionKey() const { | ||
| 327 | return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; | ||
| 328 | } | ||
| 329 | |||
| 330 | const void* NcaReader::GetExternalDecryptionKey() const { | ||
| 331 | return m_external_decryption_key.data(); | ||
| 332 | } | ||
| 333 | |||
| 334 | void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { | ||
| 335 | ASSERT(src != nullptr); | ||
| 336 | ASSERT(size == sizeof(m_external_decryption_key)); | ||
| 337 | |||
| 338 | std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key)); | ||
| 339 | } | ||
| 340 | |||
| 341 | void NcaReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 342 | ASSERT(m_body_storage != nullptr); | ||
| 343 | ASSERT(dst != nullptr); | ||
| 344 | ASSERT(dst_size >= sizeof(NcaHeader)); | ||
| 345 | |||
| 346 | std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); | ||
| 347 | } | ||
| 348 | |||
| 349 | GetDecompressorFunction NcaReader::GetDecompressor() const { | ||
| 350 | ASSERT(m_get_decompressor != nullptr); | ||
| 351 | return m_get_decompressor; | ||
| 352 | } | ||
| 353 | |||
| 354 | NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { | ||
| 355 | return m_header_encryption_type; | ||
| 356 | } | ||
| 357 | |||
| 358 | Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { | ||
| 359 | ASSERT(dst != nullptr); | ||
| 360 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 361 | |||
| 362 | const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; | ||
| 363 | m_header_storage->ReadObject(dst, offset); | ||
| 364 | |||
| 365 | R_SUCCEED(); | ||
| 366 | } | ||
| 367 | |||
| 368 | bool NcaReader::GetHeaderSign1Valid() const { | ||
| 369 | return m_is_header_sign1_signature_valid; | ||
| 370 | } | ||
| 371 | |||
| 372 | void NcaReader::GetHeaderSign2(void* dst, size_t size) const { | ||
| 373 | ASSERT(dst != nullptr); | ||
| 374 | ASSERT(size == NcaHeader::HeaderSignSize); | ||
| 375 | |||
| 376 | std::memcpy(dst, m_header.header_sign_2.data(), size); | ||
| 377 | } | ||
| 378 | |||
| 379 | Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { | ||
| 380 | // Reset ourselves to uninitialized. | ||
| 381 | m_fs_index = -1; | ||
| 382 | |||
| 383 | // Read the header. | ||
| 384 | R_TRY(reader.ReadHeader(std::addressof(m_data), index)); | ||
| 385 | |||
| 386 | // Set our index. | ||
| 387 | m_fs_index = index; | ||
| 388 | R_SUCCEED(); | ||
| 389 | } | ||
| 390 | |||
| 391 | void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 392 | ASSERT(this->IsInitialized()); | ||
| 393 | ASSERT(dst != nullptr); | ||
| 394 | ASSERT(dst_size >= sizeof(NcaFsHeader)); | ||
| 395 | |||
| 396 | std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); | ||
| 397 | } | ||
| 398 | |||
| 399 | NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { | ||
| 400 | ASSERT(this->IsInitialized()); | ||
| 401 | return m_data.hash_data; | ||
| 402 | } | ||
| 403 | |||
| 404 | const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { | ||
| 405 | ASSERT(this->IsInitialized()); | ||
| 406 | return m_data.hash_data; | ||
| 407 | } | ||
| 408 | |||
| 409 | u16 NcaFsHeaderReader::GetVersion() const { | ||
| 410 | ASSERT(this->IsInitialized()); | ||
| 411 | return m_data.version; | ||
| 412 | } | ||
| 413 | |||
| 414 | s32 NcaFsHeaderReader::GetFsIndex() const { | ||
| 415 | ASSERT(this->IsInitialized()); | ||
| 416 | return m_fs_index; | ||
| 417 | } | ||
| 418 | |||
| 419 | NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { | ||
| 420 | ASSERT(this->IsInitialized()); | ||
| 421 | return m_data.fs_type; | ||
| 422 | } | ||
| 423 | |||
| 424 | NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { | ||
| 425 | ASSERT(this->IsInitialized()); | ||
| 426 | return m_data.hash_type; | ||
| 427 | } | ||
| 428 | |||
| 429 | NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { | ||
| 430 | ASSERT(this->IsInitialized()); | ||
| 431 | return m_data.encryption_type; | ||
| 432 | } | ||
| 433 | |||
| 434 | NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { | ||
| 435 | ASSERT(this->IsInitialized()); | ||
| 436 | return m_data.patch_info; | ||
| 437 | } | ||
| 438 | |||
| 439 | const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { | ||
| 440 | ASSERT(this->IsInitialized()); | ||
| 441 | return m_data.patch_info; | ||
| 442 | } | ||
| 443 | |||
| 444 | const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { | ||
| 445 | ASSERT(this->IsInitialized()); | ||
| 446 | return m_data.aes_ctr_upper_iv; | ||
| 447 | } | ||
| 448 | |||
| 449 | bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { | ||
| 450 | ASSERT(this->IsInitialized()); | ||
| 451 | return m_data.IsSkipLayerHashEncryption(); | ||
| 452 | } | ||
| 453 | |||
| 454 | Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { | ||
| 455 | ASSERT(out != nullptr); | ||
| 456 | ASSERT(this->IsInitialized()); | ||
| 457 | |||
| 458 | R_RETURN(m_data.GetHashTargetOffset(out)); | ||
| 459 | } | ||
| 460 | |||
| 461 | bool NcaFsHeaderReader::ExistsSparseLayer() const { | ||
| 462 | ASSERT(this->IsInitialized()); | ||
| 463 | return m_data.sparse_info.generation != 0; | ||
| 464 | } | ||
| 465 | |||
| 466 | NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { | ||
| 467 | ASSERT(this->IsInitialized()); | ||
| 468 | return m_data.sparse_info; | ||
| 469 | } | ||
| 470 | |||
| 471 | const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { | ||
| 472 | ASSERT(this->IsInitialized()); | ||
| 473 | return m_data.sparse_info; | ||
| 474 | } | ||
| 475 | |||
| 476 | bool NcaFsHeaderReader::ExistsCompressionLayer() const { | ||
| 477 | ASSERT(this->IsInitialized()); | ||
| 478 | return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; | ||
| 479 | } | ||
| 480 | |||
| 481 | NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { | ||
| 482 | ASSERT(this->IsInitialized()); | ||
| 483 | return m_data.compression_info; | ||
| 484 | } | ||
| 485 | |||
| 486 | const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { | ||
| 487 | ASSERT(this->IsInitialized()); | ||
| 488 | return m_data.compression_info; | ||
| 489 | } | ||
| 490 | |||
| 491 | bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { | ||
| 492 | ASSERT(this->IsInitialized()); | ||
| 493 | return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); | ||
| 494 | } | ||
| 495 | |||
| 496 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { | ||
| 497 | ASSERT(this->IsInitialized()); | ||
| 498 | return m_data.meta_data_hash_data_info; | ||
| 499 | } | ||
| 500 | |||
| 501 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { | ||
| 502 | ASSERT(this->IsInitialized()); | ||
| 503 | return m_data.meta_data_hash_data_info; | ||
| 504 | } | ||
| 505 | |||
| 506 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { | ||
| 507 | ASSERT(this->IsInitialized()); | ||
| 508 | return m_data.meta_data_hash_type; | ||
| 509 | } | ||
| 510 | |||
| 511 | bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { | ||
| 512 | ASSERT(this->IsInitialized()); | ||
| 513 | return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); | ||
| 514 | } | ||
| 515 | |||
| 516 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { | ||
| 517 | ASSERT(this->IsInitialized()); | ||
| 518 | return m_data.meta_data_hash_data_info; | ||
| 519 | } | ||
| 520 | |||
| 521 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { | ||
| 522 | ASSERT(this->IsInitialized()); | ||
| 523 | return m_data.meta_data_hash_data_info; | ||
| 524 | } | ||
| 525 | |||
| 526 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { | ||
| 527 | ASSERT(this->IsInitialized()); | ||
| 528 | return m_data.meta_data_hash_type; | ||
| 529 | } | ||
| 530 | |||
| 531 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp new file mode 100644 index 000000000..bbfaab255 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | constexpr size_t HeapBlockSize = BufferPoolAlignment; | ||
| 12 | static_assert(HeapBlockSize == 4_KiB); | ||
| 13 | |||
| 14 | // A heap block is 4KiB. An order is a power of two. | ||
| 15 | // This gives blocks of the order 32KiB, 512KiB, 4MiB. | ||
| 16 | constexpr s32 HeapOrderMax = 7; | ||
| 17 | constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; | ||
| 18 | |||
| 19 | constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); | ||
| 20 | constexpr size_t HeapAllocatableSizeMaxForLarge = | ||
| 21 | HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { | ||
| 26 | return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; | ||
| 27 | } | ||
| 28 | |||
| 29 | void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { | ||
| 30 | // Ensure preconditions. | ||
| 31 | ASSERT(m_buffer == nullptr); | ||
| 32 | |||
| 33 | // Check that we can allocate this size. | ||
| 34 | ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); | ||
| 35 | |||
| 36 | const size_t target_size = | ||
| 37 | std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); | ||
| 38 | |||
| 39 | // Dummy implementation for allocate. | ||
| 40 | if (target_size > 0) { | ||
| 41 | m_buffer = | ||
| 42 | reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize})); | ||
| 43 | m_size = target_size; | ||
| 44 | |||
| 45 | // Ensure postconditions. | ||
| 46 | ASSERT(m_buffer != nullptr); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | void PooledBuffer::Shrink(size_t ideal_size) { | ||
| 51 | ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); | ||
| 52 | |||
| 53 | // Shrinking to zero means that we have no buffer. | ||
| 54 | if (ideal_size == 0) { | ||
| 55 | ::operator delete(m_buffer, std::align_val_t{HeapBlockSize}); | ||
| 56 | m_buffer = nullptr; | ||
| 57 | m_size = ideal_size; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h new file mode 100644 index 000000000..9a6adbcb5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/hle/result.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | using namespace Common::Literals; | ||
| 14 | |||
| 15 | constexpr inline size_t BufferPoolAlignment = 4_KiB; | ||
| 16 | constexpr inline size_t BufferPoolWorkSize = 320; | ||
| 17 | |||
| 18 | class PooledBuffer { | ||
| 19 | YUZU_NON_COPYABLE(PooledBuffer); | ||
| 20 | |||
| 21 | public: | ||
| 22 | // Constructor/Destructor. | ||
| 23 | constexpr PooledBuffer() : m_buffer(), m_size() {} | ||
| 24 | |||
| 25 | PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { | ||
| 26 | this->Allocate(ideal_size, required_size); | ||
| 27 | } | ||
| 28 | |||
| 29 | ~PooledBuffer() { | ||
| 30 | this->Deallocate(); | ||
| 31 | } | ||
| 32 | |||
| 33 | // Move and assignment. | ||
| 34 | explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { | ||
| 35 | rhs.m_buffer = nullptr; | ||
| 36 | rhs.m_size = 0; | ||
| 37 | } | ||
| 38 | |||
| 39 | PooledBuffer& operator=(PooledBuffer&& rhs) { | ||
| 40 | PooledBuffer(std::move(rhs)).Swap(*this); | ||
| 41 | return *this; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Allocation API. | ||
| 45 | void Allocate(size_t ideal_size, size_t required_size) { | ||
| 46 | return this->AllocateCore(ideal_size, required_size, false); | ||
| 47 | } | ||
| 48 | |||
| 49 | void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { | ||
| 50 | return this->AllocateCore(ideal_size, required_size, true); | ||
| 51 | } | ||
| 52 | |||
| 53 | void Shrink(size_t ideal_size); | ||
| 54 | |||
| 55 | void Deallocate() { | ||
| 56 | // Shrink the buffer to empty. | ||
| 57 | this->Shrink(0); | ||
| 58 | ASSERT(m_buffer == nullptr); | ||
| 59 | } | ||
| 60 | |||
| 61 | char* GetBuffer() const { | ||
| 62 | ASSERT(m_buffer != nullptr); | ||
| 63 | return m_buffer; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t GetSize() const { | ||
| 67 | ASSERT(m_buffer != nullptr); | ||
| 68 | return m_size; | ||
| 69 | } | ||
| 70 | |||
| 71 | public: | ||
| 72 | static size_t GetAllocatableSizeMax() { | ||
| 73 | return GetAllocatableSizeMaxCore(false); | ||
| 74 | } | ||
| 75 | static size_t GetAllocatableParticularlyLargeSizeMax() { | ||
| 76 | return GetAllocatableSizeMaxCore(true); | ||
| 77 | } | ||
| 78 | |||
| 79 | private: | ||
| 80 | static size_t GetAllocatableSizeMaxCore(bool large); | ||
| 81 | |||
| 82 | private: | ||
| 83 | void Swap(PooledBuffer& rhs) { | ||
| 84 | std::swap(m_buffer, rhs.m_buffer); | ||
| 85 | std::swap(m_size, rhs.m_size); | ||
| 86 | } | ||
| 87 | |||
| 88 | void AllocateCore(size_t ideal_size, size_t required_size, bool large); | ||
| 89 | |||
| 90 | private: | ||
| 91 | char* m_buffer; | ||
| 92 | size_t m_size; | ||
| 93 | }; | ||
| 94 | |||
| 95 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 000000000..8574a11dd --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 9 | // Validate preconditions. | ||
| 10 | ASSERT(this->IsInitialized()); | ||
| 11 | ASSERT(buffer != nullptr); | ||
| 12 | |||
| 13 | // Allow zero size. | ||
| 14 | if (size == 0) { | ||
| 15 | return size; | ||
| 16 | } | ||
| 17 | |||
| 18 | SparseStorage* self = const_cast<SparseStorage*>(this); | ||
| 19 | |||
| 20 | if (self->GetEntryTable().IsEmpty()) { | ||
| 21 | BucketTree::Offsets table_offsets; | ||
| 22 | ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); | ||
| 23 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 24 | |||
| 25 | std::memset(buffer, 0, size); | ||
| 26 | } else { | ||
| 27 | self->OperatePerEntry<false, true>( | ||
| 28 | offset, size, | ||
| 29 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 30 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 31 | static_cast<size_t>(cur_size), data_offset); | ||
| 32 | R_SUCCEED(); | ||
| 33 | }); | ||
| 34 | } | ||
| 35 | |||
| 36 | return size; | ||
| 37 | } | ||
| 38 | |||
| 39 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h new file mode 100644 index 000000000..6c196ec61 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class SparseStorage : public IndirectStorage { | ||
| 11 | YUZU_NON_COPYABLE(SparseStorage); | ||
| 12 | YUZU_NON_MOVEABLE(SparseStorage); | ||
| 13 | |||
| 14 | private: | ||
| 15 | class ZeroStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | ZeroStorage() {} | ||
| 18 | virtual ~ZeroStorage() {} | ||
| 19 | |||
| 20 | virtual size_t GetSize() const override { | ||
| 21 | return std::numeric_limits<size_t>::max(); | ||
| 22 | } | ||
| 23 | |||
| 24 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 25 | ASSERT(buffer != nullptr || size == 0); | ||
| 26 | |||
| 27 | if (size > 0) { | ||
| 28 | std::memset(buffer, 0, size); | ||
| 29 | } | ||
| 30 | |||
| 31 | return size; | ||
| 32 | } | ||
| 33 | }; | ||
| 34 | |||
| 35 | public: | ||
| 36 | SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {} | ||
| 37 | virtual ~SparseStorage() {} | ||
| 38 | |||
| 39 | using IndirectStorage::Initialize; | ||
| 40 | |||
| 41 | void Initialize(s64 end_offset) { | ||
| 42 | this->GetEntryTable().Initialize(NodeSize, end_offset); | ||
| 43 | this->SetZeroStorage(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void SetDataStorage(VirtualFile storage) { | ||
| 47 | ASSERT(this->IsInitialized()); | ||
| 48 | |||
| 49 | this->SetStorage(0, storage); | ||
| 50 | this->SetZeroStorage(); | ||
| 51 | } | ||
| 52 | |||
| 53 | template <typename T> | ||
| 54 | void SetDataStorage(T storage, s64 offset, s64 size) { | ||
| 55 | ASSERT(this->IsInitialized()); | ||
| 56 | |||
| 57 | this->SetStorage(0, storage, offset, size); | ||
| 58 | this->SetZeroStorage(); | ||
| 59 | } | ||
| 60 | |||
| 61 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 62 | |||
| 63 | private: | ||
| 64 | void SetZeroStorage() { | ||
| 65 | return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max()); | ||
| 66 | } | ||
| 67 | |||
| 68 | private: | ||
| 69 | VirtualFile m_zero_storage; | ||
| 70 | }; | ||
| 71 | |||
| 72 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h new file mode 100644 index 000000000..2b43927cb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class RegionSwitchStorage : public IReadOnlyStorage { | ||
| 11 | YUZU_NON_COPYABLE(RegionSwitchStorage); | ||
| 12 | YUZU_NON_MOVEABLE(RegionSwitchStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | struct Region { | ||
| 16 | s64 offset; | ||
| 17 | s64 size; | ||
| 18 | }; | ||
| 19 | |||
| 20 | public: | ||
| 21 | RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r) | ||
| 22 | : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), | ||
| 23 | m_region(r) {} | ||
| 24 | |||
| 25 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 26 | // Process until we're done. | ||
| 27 | size_t processed = 0; | ||
| 28 | while (processed < size) { | ||
| 29 | // Process on the appropriate storage. | ||
| 30 | s64 cur_size = 0; | ||
| 31 | if (this->CheckRegions(std::addressof(cur_size), offset + processed, | ||
| 32 | size - processed)) { | ||
| 33 | m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 34 | } else { | ||
| 35 | m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 36 | } | ||
| 37 | |||
| 38 | // Advance. | ||
| 39 | processed += cur_size; | ||
| 40 | } | ||
| 41 | |||
| 42 | return size; | ||
| 43 | } | ||
| 44 | |||
| 45 | virtual size_t GetSize() const override { | ||
| 46 | return m_inside_region_storage->GetSize(); | ||
| 47 | } | ||
| 48 | |||
| 49 | private: | ||
| 50 | bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const { | ||
| 51 | // Check if our region contains the access. | ||
| 52 | if (m_region.offset <= offset) { | ||
| 53 | if (offset < m_region.offset + m_region.size) { | ||
| 54 | if (m_region.offset + m_region.size <= offset + size) { | ||
| 55 | *out_current_size = m_region.offset + m_region.size - offset; | ||
| 56 | } else { | ||
| 57 | *out_current_size = size; | ||
| 58 | } | ||
| 59 | return true; | ||
| 60 | } else { | ||
| 61 | *out_current_size = size; | ||
| 62 | return false; | ||
| 63 | } | ||
| 64 | } else { | ||
| 65 | if (m_region.offset <= offset + size) { | ||
| 66 | *out_current_size = m_region.offset - offset; | ||
| 67 | } else { | ||
| 68 | *out_current_size = size; | ||
| 69 | } | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | private: | ||
| 75 | VirtualFile m_inside_region_storage; | ||
| 76 | VirtualFile m_outside_region_storage; | ||
| 77 | Region m_region; | ||
| 78 | }; | ||
| 79 | |||
| 80 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp new file mode 100644 index 000000000..ceabb8ff1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.cpp | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | void AddCounter(void* counter_, size_t counter_size, u64 value) { | ||
| 9 | u8* counter = static_cast<u8*>(counter_); | ||
| 10 | u64 remaining = value; | ||
| 11 | u8 carry = 0; | ||
| 12 | |||
| 13 | for (size_t i = 0; i < counter_size; i++) { | ||
| 14 | auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; | ||
| 15 | carry = static_cast<u8>(sum >> (sizeof(u8) * 8)); | ||
| 16 | auto sum8 = static_cast<u8>(sum & 0xFF); | ||
| 17 | |||
| 18 | counter[counter_size - 1 - i] = sum8; | ||
| 19 | |||
| 20 | remaining >>= (sizeof(u8) * 8); | ||
| 21 | if (carry == 0 && remaining == 0) { | ||
| 22 | break; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h new file mode 100644 index 000000000..284b8b811 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | void AddCounter(void* counter, size_t counter_size, u64 value); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index efdf18cee..7be1322cc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp | |||
| @@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) { | |||
| 165 | void IPSwitchCompiler::ParseFlag(const std::string& line) { | 165 | void IPSwitchCompiler::ParseFlag(const std::string& line) { |
| 166 | if (StartsWith(line, "@flag offset_shift ")) { | 166 | if (StartsWith(line, "@flag offset_shift ")) { |
| 167 | // Offset Shift Flag | 167 | // Offset Shift Flag |
| 168 | offset_shift = std::stoll(line.substr(19), nullptr, 0); | 168 | offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0); |
| 169 | } else if (StartsWith(line, "@little-endian")) { | 169 | } else if (StartsWith(line, "@little-endian")) { |
| 170 | // Set values to read as little endian | 170 | // Set values to read as little endian |
| 171 | is_little_endian = true; | 171 | is_little_endian = true; |
| @@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() { | |||
| 263 | // 11 - 8 hex digit offset + space + minimum two digit overwrite val | 263 | // 11 - 8 hex digit offset + space + minimum two digit overwrite val |
| 264 | if (patch_line.length() < 11) | 264 | if (patch_line.length() < 11) |
| 265 | break; | 265 | break; |
| 266 | auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); | 266 | auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16); |
| 267 | offset += static_cast<unsigned long>(offset_shift); | 267 | offset += static_cast<unsigned long>(offset_shift); |
| 268 | 268 | ||
| 269 | std::vector<u8> replace; | 269 | std::vector<u8> replace; |
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 52c78020c..f4a774675 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp | |||
| @@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_, | |||
| 45 | 45 | ||
| 46 | CNMT::~CNMT() = default; | 46 | CNMT::~CNMT() = default; |
| 47 | 47 | ||
| 48 | const CNMTHeader& CNMT::GetHeader() const { | ||
| 49 | return header; | ||
| 50 | } | ||
| 51 | |||
| 48 | u64 CNMT::GetTitleID() const { | 52 | u64 CNMT::GetTitleID() const { |
| 49 | return header.title_id; | 53 | return header.title_id; |
| 50 | } | 54 | } |
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index c59ece010..68e463b5f 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h | |||
| @@ -89,6 +89,7 @@ public: | |||
| 89 | std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); | 89 | std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); |
| 90 | ~CNMT(); | 90 | ~CNMT(); |
| 91 | 91 | ||
| 92 | const CNMTHeader& GetHeader() const; | ||
| 92 | u64 GetTitleID() const; | 93 | u64 GetTitleID() const; |
| 93 | u32 GetTitleVersion() const; | 94 | u32 GetTitleVersion() const; |
| 94 | TitleType GetType() const; | 95 | TitleType GetType() const; |
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp deleted file mode 100644 index 2735d053b..000000000 --- a/src/core/file_sys/nca_patch.cpp +++ /dev/null | |||
| @@ -1,217 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <array> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <cstring> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "core/crypto/aes_util.h" | ||
| 11 | #include "core/file_sys/nca_patch.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | namespace { | ||
| 15 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 16 | std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, | ||
| 17 | const BucketType& buckets) { | ||
| 18 | if constexpr (Subsection) { | ||
| 19 | const auto& last_bucket = buckets[block.number_buckets - 1]; | ||
| 20 | if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { | ||
| 21 | return {block.number_buckets - 1, last_bucket.number_entries}; | ||
| 22 | } | ||
| 23 | } else { | ||
| 24 | ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); | ||
| 25 | } | ||
| 26 | |||
| 27 | std::size_t bucket_id = std::count_if( | ||
| 28 | block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, | ||
| 29 | [&offset](u64 base_offset) { return base_offset <= offset; }); | ||
| 30 | |||
| 31 | const auto& bucket = buckets[bucket_id]; | ||
| 32 | |||
| 33 | if (bucket.number_entries == 1) { | ||
| 34 | return {bucket_id, 0}; | ||
| 35 | } | ||
| 36 | |||
| 37 | std::size_t low = 0; | ||
| 38 | std::size_t mid = 0; | ||
| 39 | std::size_t high = bucket.number_entries - 1; | ||
| 40 | while (low <= high) { | ||
| 41 | mid = (low + high) / 2; | ||
| 42 | if (bucket.entries[mid].address_patch > offset) { | ||
| 43 | high = mid - 1; | ||
| 44 | } else { | ||
| 45 | if (mid == bucket.number_entries - 1 || | ||
| 46 | bucket.entries[mid + 1].address_patch > offset) { | ||
| 47 | return {bucket_id, mid}; | ||
| 48 | } | ||
| 49 | |||
| 50 | low = mid + 1; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | ASSERT_MSG(false, "Offset could not be found in BKTR block."); | ||
| 54 | return {0, 0}; | ||
| 55 | } | ||
| 56 | } // Anonymous namespace | ||
| 57 | |||
| 58 | BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, | ||
| 59 | std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, | ||
| 60 | std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, | ||
| 61 | Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, | ||
| 62 | std::array<u8, 8> section_ctr_) | ||
| 63 | : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), | ||
| 64 | subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), | ||
| 65 | base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), | ||
| 66 | encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), | ||
| 67 | section_ctr(section_ctr_) { | ||
| 68 | for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { | ||
| 69 | relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); | ||
| 70 | } | ||
| 71 | |||
| 72 | for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { | ||
| 73 | subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, | ||
| 74 | {0}, | ||
| 75 | subsection_buckets[i + 1].entries[0].ctr}); | ||
| 76 | } | ||
| 77 | |||
| 78 | relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); | ||
| 79 | } | ||
| 80 | |||
| 81 | BKTR::~BKTR() = default; | ||
| 82 | |||
| 83 | std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { | ||
| 84 | // Read out of bounds. | ||
| 85 | if (offset >= relocation.size) { | ||
| 86 | return 0; | ||
| 87 | } | ||
| 88 | |||
| 89 | const auto relocation_entry = GetRelocationEntry(offset); | ||
| 90 | const auto section_offset = | ||
| 91 | offset - relocation_entry.address_patch + relocation_entry.address_source; | ||
| 92 | const auto bktr_read = relocation_entry.from_patch; | ||
| 93 | |||
| 94 | const auto next_relocation = GetNextRelocationEntry(offset); | ||
| 95 | |||
| 96 | if (offset + length > next_relocation.address_patch) { | ||
| 97 | const u64 partition = next_relocation.address_patch - offset; | ||
| 98 | return Read(data, partition, offset) + | ||
| 99 | Read(data + partition, length - partition, offset + partition); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!bktr_read) { | ||
| 103 | ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); | ||
| 104 | return base_romfs->Read(data, length, section_offset - ivfc_offset); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (!encrypted) { | ||
| 108 | return bktr_romfs->Read(data, length, section_offset); | ||
| 109 | } | ||
| 110 | |||
| 111 | const auto subsection_entry = GetSubsectionEntry(section_offset); | ||
| 112 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); | ||
| 113 | |||
| 114 | // Calculate AES IV | ||
| 115 | std::array<u8, 16> iv{}; | ||
| 116 | auto subsection_ctr = subsection_entry.ctr; | ||
| 117 | auto offset_iv = section_offset + base_offset; | ||
| 118 | for (std::size_t i = 0; i < section_ctr.size(); ++i) { | ||
| 119 | iv[i] = section_ctr[0x8 - i - 1]; | ||
| 120 | } | ||
| 121 | offset_iv >>= 4; | ||
| 122 | for (std::size_t i = 0; i < sizeof(u64); ++i) { | ||
| 123 | iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); | ||
| 124 | offset_iv >>= 8; | ||
| 125 | } | ||
| 126 | for (std::size_t i = 0; i < sizeof(u32); ++i) { | ||
| 127 | iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); | ||
| 128 | subsection_ctr >>= 8; | ||
| 129 | } | ||
| 130 | cipher.SetIV(iv); | ||
| 131 | |||
| 132 | const auto next_subsection = GetNextSubsectionEntry(section_offset); | ||
| 133 | |||
| 134 | if (section_offset + length > next_subsection.address_patch) { | ||
| 135 | const u64 partition = next_subsection.address_patch - section_offset; | ||
| 136 | return Read(data, partition, offset) + | ||
| 137 | Read(data + partition, length - partition, offset + partition); | ||
| 138 | } | ||
| 139 | |||
| 140 | const auto block_offset = section_offset & 0xF; | ||
| 141 | if (block_offset != 0) { | ||
| 142 | auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); | ||
| 143 | cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); | ||
| 144 | if (length + block_offset < 0x10) { | ||
| 145 | std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); | ||
| 146 | return std::min(length, block.size()); | ||
| 147 | } | ||
| 148 | |||
| 149 | const auto read = 0x10 - block_offset; | ||
| 150 | std::memcpy(data, block.data() + block_offset, read); | ||
| 151 | return read + Read(data + read, length - read, offset + read); | ||
| 152 | } | ||
| 153 | |||
| 154 | const auto raw_read = bktr_romfs->Read(data, length, section_offset); | ||
| 155 | cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); | ||
| 156 | return raw_read; | ||
| 157 | } | ||
| 158 | |||
| 159 | RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { | ||
| 160 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 161 | return relocation_buckets[res.first].entries[res.second]; | ||
| 162 | } | ||
| 163 | |||
| 164 | RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { | ||
| 165 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 166 | const auto bucket = relocation_buckets[res.first]; | ||
| 167 | if (res.second + 1 < bucket.entries.size()) | ||
| 168 | return bucket.entries[res.second + 1]; | ||
| 169 | return relocation_buckets[res.first + 1].entries[0]; | ||
| 170 | } | ||
| 171 | |||
| 172 | SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { | ||
| 173 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 174 | return subsection_buckets[res.first].entries[res.second]; | ||
| 175 | } | ||
| 176 | |||
| 177 | SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { | ||
| 178 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 179 | const auto bucket = subsection_buckets[res.first]; | ||
| 180 | if (res.second + 1 < bucket.entries.size()) | ||
| 181 | return bucket.entries[res.second + 1]; | ||
| 182 | return subsection_buckets[res.first + 1].entries[0]; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::string BKTR::GetName() const { | ||
| 186 | return base_romfs->GetName(); | ||
| 187 | } | ||
| 188 | |||
| 189 | std::size_t BKTR::GetSize() const { | ||
| 190 | return relocation.size; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool BKTR::Resize(std::size_t new_size) { | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | VirtualDir BKTR::GetContainingDirectory() const { | ||
| 198 | return base_romfs->GetContainingDirectory(); | ||
| 199 | } | ||
| 200 | |||
| 201 | bool BKTR::IsWritable() const { | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | bool BKTR::IsReadable() const { | ||
| 206 | return true; | ||
| 207 | } | ||
| 208 | |||
| 209 | std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { | ||
| 210 | return 0; | ||
| 211 | } | ||
| 212 | |||
| 213 | bool BKTR::Rename(std::string_view name) { | ||
| 214 | return base_romfs->Rename(name); | ||
| 215 | } | ||
| 216 | |||
| 217 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h deleted file mode 100644 index 595e3ef09..000000000 --- a/src/core/file_sys/nca_patch.h +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/swap.h" | ||
| 13 | #include "core/crypto/key_manager.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | #pragma pack(push, 1) | ||
| 18 | struct RelocationEntry { | ||
| 19 | u64_le address_patch; | ||
| 20 | u64_le address_source; | ||
| 21 | u32 from_patch; | ||
| 22 | }; | ||
| 23 | #pragma pack(pop) | ||
| 24 | static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); | ||
| 25 | |||
| 26 | struct RelocationBucketRaw { | ||
| 27 | INSERT_PADDING_BYTES(4); | ||
| 28 | u32_le number_entries; | ||
| 29 | u64_le end_offset; | ||
| 30 | std::array<RelocationEntry, 0x332> relocation_entries; | ||
| 31 | INSERT_PADDING_BYTES(8); | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); | ||
| 34 | |||
| 35 | // Vector version of RelocationBucketRaw | ||
| 36 | struct RelocationBucket { | ||
| 37 | u32 number_entries; | ||
| 38 | u64 end_offset; | ||
| 39 | std::vector<RelocationEntry> entries; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct RelocationBlock { | ||
| 43 | INSERT_PADDING_BYTES(4); | ||
| 44 | u32_le number_buckets; | ||
| 45 | u64_le size; | ||
| 46 | std::array<u64, 0x7FE> base_offsets; | ||
| 47 | }; | ||
| 48 | static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); | ||
| 49 | |||
| 50 | struct SubsectionEntry { | ||
| 51 | u64_le address_patch; | ||
| 52 | INSERT_PADDING_BYTES(0x4); | ||
| 53 | u32_le ctr; | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); | ||
| 56 | |||
| 57 | struct SubsectionBucketRaw { | ||
| 58 | INSERT_PADDING_BYTES(4); | ||
| 59 | u32_le number_entries; | ||
| 60 | u64_le end_offset; | ||
| 61 | std::array<SubsectionEntry, 0x3FF> subsection_entries; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); | ||
| 64 | |||
| 65 | // Vector version of SubsectionBucketRaw | ||
| 66 | struct SubsectionBucket { | ||
| 67 | u32 number_entries; | ||
| 68 | u64 end_offset; | ||
| 69 | std::vector<SubsectionEntry> entries; | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct SubsectionBlock { | ||
| 73 | INSERT_PADDING_BYTES(4); | ||
| 74 | u32_le number_buckets; | ||
| 75 | u64_le size; | ||
| 76 | std::array<u64, 0x7FE> base_offsets; | ||
| 77 | }; | ||
| 78 | static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); | ||
| 79 | |||
| 80 | inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { | ||
| 81 | return {raw.number_entries, | ||
| 82 | raw.end_offset, | ||
| 83 | {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; | ||
| 84 | } | ||
| 85 | |||
| 86 | inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { | ||
| 87 | return {raw.number_entries, | ||
| 88 | raw.end_offset, | ||
| 89 | {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; | ||
| 90 | } | ||
| 91 | |||
| 92 | class BKTR : public VfsFile { | ||
| 93 | public: | ||
| 94 | BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, | ||
| 95 | std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, | ||
| 96 | std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, | ||
| 97 | Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); | ||
| 98 | ~BKTR() override; | ||
| 99 | |||
| 100 | std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; | ||
| 101 | |||
| 102 | std::string GetName() const override; | ||
| 103 | |||
| 104 | std::size_t GetSize() const override; | ||
| 105 | |||
| 106 | bool Resize(std::size_t new_size) override; | ||
| 107 | |||
| 108 | VirtualDir GetContainingDirectory() const override; | ||
| 109 | |||
| 110 | bool IsWritable() const override; | ||
| 111 | |||
| 112 | bool IsReadable() const override; | ||
| 113 | |||
| 114 | std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; | ||
| 115 | |||
| 116 | bool Rename(std::string_view name) override; | ||
| 117 | |||
| 118 | private: | ||
| 119 | RelocationEntry GetRelocationEntry(u64 offset) const; | ||
| 120 | RelocationEntry GetNextRelocationEntry(u64 offset) const; | ||
| 121 | |||
| 122 | SubsectionEntry GetSubsectionEntry(u64 offset) const; | ||
| 123 | SubsectionEntry GetNextSubsectionEntry(u64 offset) const; | ||
| 124 | |||
| 125 | RelocationBlock relocation; | ||
| 126 | std::vector<RelocationBucket> relocation_buckets; | ||
| 127 | SubsectionBlock subsection; | ||
| 128 | std::vector<SubsectionBucket> subsection_buckets; | ||
| 129 | |||
| 130 | // Should be the raw base romfs, decrypted. | ||
| 131 | VirtualFile base_romfs; | ||
| 132 | // Should be the raw BKTR romfs, (located at media_offset with size media_size). | ||
| 133 | VirtualFile bktr_romfs; | ||
| 134 | |||
| 135 | bool encrypted; | ||
| 136 | Core::Crypto::Key128 key; | ||
| 137 | |||
| 138 | // Base offset into NCA, used for IV calculation. | ||
| 139 | u64 base_offset; | ||
| 140 | // Distance between IVFC start and RomFS start, used for base reads | ||
| 141 | u64 ivfc_offset; | ||
| 142 | std::array<u8, 8> section_ctr; | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 2ba1b34a4..8e475f25a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { | |||
| 141 | const auto update_tid = GetUpdateTitleID(title_id); | 141 | const auto update_tid = GetUpdateTitleID(title_id); |
| 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); | 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); |
| 143 | 143 | ||
| 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && | 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { |
| 145 | update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||
| 146 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", | 145 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", |
| 147 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 146 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 148 | exefs = update->GetExeFS(); | 147 | exefs = update->GetExeFS(); |
| @@ -295,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st | |||
| 295 | return out; | 294 | return out; |
| 296 | } | 295 | } |
| 297 | 296 | ||
| 298 | bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { | 297 | bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const { |
| 299 | const auto build_id_raw = Common::HexToString(build_id_); | 298 | const auto build_id_raw = Common::HexToString(build_id_); |
| 300 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); | 299 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); |
| 301 | 300 | ||
| 302 | LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); | 301 | LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); |
| 303 | 302 | ||
| 304 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 303 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 305 | if (load_dir == nullptr) { | 304 | if (load_dir == nullptr) { |
| @@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 353 | const Service::FileSystem::FileSystemController& fs_controller) { | 352 | const Service::FileSystem::FileSystemController& fs_controller) { |
| 354 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 353 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 355 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); | 354 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); |
| 356 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || | 355 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data && |
| 356 | type != ContentRecordType::HtmlDocument) || | ||
| 357 | (load_dir == nullptr && sdmc_load_dir == nullptr)) { | 357 | (load_dir == nullptr && sdmc_load_dir == nullptr)) { |
| 358 | return; | 358 | return; |
| 359 | } | 359 | } |
| 360 | 360 | ||
| 361 | auto extracted = ExtractRomFS(romfs); | ||
| 362 | if (extracted == nullptr) { | ||
| 363 | return; | ||
| 364 | } | ||
| 365 | |||
| 366 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 361 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 367 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); | 362 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); |
| 368 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { | 363 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { |
| @@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 387 | auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); | 382 | auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); |
| 388 | if (ext_dir != nullptr) | 383 | if (ext_dir != nullptr) |
| 389 | layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); | 384 | layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); |
| 385 | |||
| 386 | if (type == ContentRecordType::HtmlDocument) { | ||
| 387 | auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html"); | ||
| 388 | if (manual_dir != nullptr) | ||
| 389 | layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir)); | ||
| 390 | } | ||
| 390 | } | 391 | } |
| 391 | 392 | ||
| 392 | // When there are no layers to apply, return early as there is no need to rebuild the RomFS | 393 | // When there are no layers to apply, return early as there is no need to rebuild the RomFS |
| @@ -394,6 +395,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 394 | return; | 395 | return; |
| 395 | } | 396 | } |
| 396 | 397 | ||
| 398 | auto extracted = ExtractRomFS(romfs); | ||
| 399 | if (extracted == nullptr) { | ||
| 400 | return; | ||
| 401 | } | ||
| 402 | |||
| 397 | layers.push_back(std::move(extracted)); | 403 | layers.push_back(std::move(extracted)); |
| 398 | 404 | ||
| 399 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); | 405 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); |
| @@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 412 | romfs = std::move(packed); | 418 | romfs = std::move(packed); |
| 413 | } | 419 | } |
| 414 | 420 | ||
| 415 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, | 421 | VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 416 | VirtualFile update_raw, bool apply_layeredfs) const { | 422 | ContentRecordType type, VirtualFile packed_update_raw, |
| 423 | bool apply_layeredfs) const { | ||
| 417 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", | 424 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", |
| 418 | title_id, static_cast<u8>(type)); | 425 | title_id, static_cast<u8>(type)); |
| 419 | |||
| 420 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { | 426 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { |
| 421 | LOG_INFO(Loader, "{}", log_string); | 427 | LOG_INFO(Loader, "{}", log_string); |
| 422 | } else { | 428 | } else { |
| 423 | LOG_DEBUG(Loader, "{}", log_string); | 429 | LOG_DEBUG(Loader, "{}", log_string); |
| 424 | } | 430 | } |
| 425 | 431 | ||
| 426 | if (romfs == nullptr) { | 432 | if (base_romfs == nullptr) { |
| 427 | return romfs; | 433 | return base_romfs; |
| 428 | } | 434 | } |
| 429 | 435 | ||
| 436 | auto romfs = base_romfs; | ||
| 437 | |||
| 430 | // Game Updates | 438 | // Game Updates |
| 431 | const auto update_tid = GetUpdateTitleID(title_id); | 439 | const auto update_tid = GetUpdateTitleID(title_id); |
| 432 | const auto update = content_provider.GetEntryRaw(update_tid, type); | 440 | const auto update_raw = content_provider.GetEntryRaw(update_tid, type); |
| 433 | 441 | ||
| 434 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 442 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 435 | const auto update_disabled = | 443 | const auto update_disabled = |
| 436 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); | 444 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); |
| 437 | 445 | ||
| 438 | if (!update_disabled && update != nullptr) { | 446 | if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { |
| 439 | const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); | 447 | const auto new_nca = std::make_shared<NCA>(update_raw, base_nca); |
| 440 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 441 | new_nca->GetRomFS() != nullptr) { | 449 | new_nca->GetRomFS() != nullptr) { |
| 442 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", | 450 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", |
| 443 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 451 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 444 | romfs = new_nca->GetRomFS(); | 452 | romfs = new_nca->GetRomFS(); |
| 453 | const auto version = | ||
| 454 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)); | ||
| 445 | } | 455 | } |
| 446 | } else if (!update_disabled && update_raw != nullptr) { | 456 | } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { |
| 447 | const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); | 457 | const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca); |
| 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 458 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 449 | new_nca->GetRomFS() != nullptr) { | 459 | new_nca->GetRomFS() != nullptr) { |
| 450 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); | 460 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); |
| @@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { | |||
| 608 | return {}; | 618 | return {}; |
| 609 | } | 619 | } |
| 610 | 620 | ||
| 611 | const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); | 621 | const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control); |
| 612 | if (romfs == nullptr) { | 622 | if (romfs == nullptr) { |
| 613 | return {}; | 623 | return {}; |
| 614 | } | 624 | } |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 69d15e2f8..03e9c7301 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -52,7 +52,7 @@ public: | |||
| 52 | 52 | ||
| 53 | // Checks to see if PatchNSO() will have any effect given the NSO's build ID. | 53 | // Checks to see if PatchNSO() will have any effect given the NSO's build ID. |
| 54 | // Used to prevent expensive copies in NSO loader. | 54 | // Used to prevent expensive copies in NSO loader. |
| 55 | [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; | 55 | [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const; |
| 56 | 56 | ||
| 57 | // Creates a CheatList object with all | 57 | // Creates a CheatList object with all |
| 58 | [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( | 58 | [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( |
| @@ -61,9 +61,9 @@ public: | |||
| 61 | // Currently tracked RomFS patches: | 61 | // Currently tracked RomFS patches: |
| 62 | // - Game Updates | 62 | // - Game Updates |
| 63 | // - LayeredFS | 63 | // - LayeredFS |
| 64 | [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, | 64 | [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 65 | ContentRecordType type = ContentRecordType::Program, | 65 | ContentRecordType type = ContentRecordType::Program, |
| 66 | VirtualFile update_raw = nullptr, | 66 | VirtualFile packed_update_raw = nullptr, |
| 67 | bool apply_layeredfs = true) const; | 67 | bool apply_layeredfs = true) const; |
| 68 | 68 | ||
| 69 | // Returns a vector of pairs between patch names and patch versions. | 69 | // Returns a vector of pairs between patch names and patch versions. |
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a6960170c..04da93d5c 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "common/hex_util.h" | 10 | #include "common/hex_util.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/scope_exit.h" | ||
| 12 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 13 | #include "core/file_sys/card_image.h" | 14 | #include "core/file_sys/card_image.h" |
| 14 | #include "core/file_sys/common_funcs.h" | 15 | #include "core/file_sys/common_funcs.h" |
| @@ -416,9 +417,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { | |||
| 416 | 417 | ||
| 417 | if (file == nullptr) | 418 | if (file == nullptr) |
| 418 | continue; | 419 | continue; |
| 419 | const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); | 420 | const auto nca = std::make_shared<NCA>(parser(file, id)); |
| 420 | if (nca->GetStatus() != Loader::ResultStatus::Success || | 421 | if (nca->GetStatus() != Loader::ResultStatus::Success || |
| 421 | nca->GetType() != NCAContentType::Meta) { | 422 | nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) { |
| 422 | continue; | 423 | continue; |
| 423 | } | 424 | } |
| 424 | 425 | ||
| @@ -500,7 +501,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t | |||
| 500 | const auto raw = GetEntryRaw(title_id, type); | 501 | const auto raw = GetEntryRaw(title_id, type); |
| 501 | if (raw == nullptr) | 502 | if (raw == nullptr) |
| 502 | return nullptr; | 503 | return nullptr; |
| 503 | return std::make_unique<NCA>(raw, nullptr, 0); | 504 | return std::make_unique<NCA>(raw); |
| 504 | } | 505 | } |
| 505 | 506 | ||
| 506 | template <typename T> | 507 | template <typename T> |
| @@ -606,9 +607,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex | |||
| 606 | const auto result = RemoveExistingEntry(title_id); | 607 | const auto result = RemoveExistingEntry(title_id); |
| 607 | 608 | ||
| 608 | // Install Metadata File | 609 | // Install Metadata File |
| 609 | const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); | 610 | const auto meta_result = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); |
| 610 | if (res != InstallResult::Success) { | 611 | if (meta_result != InstallResult::Success) { |
| 611 | return res; | 612 | return meta_result; |
| 612 | } | 613 | } |
| 613 | 614 | ||
| 614 | // Install all the other NCAs | 615 | // Install all the other NCAs |
| @@ -621,9 +622,19 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex | |||
| 621 | if (nca == nullptr) { | 622 | if (nca == nullptr) { |
| 622 | return InstallResult::ErrorCopyFailed; | 623 | return InstallResult::ErrorCopyFailed; |
| 623 | } | 624 | } |
| 624 | const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); | 625 | if (nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && |
| 625 | if (res2 != InstallResult::Success) { | 626 | nca->GetTitleId() != title_id) { |
| 626 | return res2; | 627 | // Create fake cnmt for patch to multiprogram application |
| 628 | const auto sub_nca_result = | ||
| 629 | InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy); | ||
| 630 | if (sub_nca_result != InstallResult::Success) { | ||
| 631 | return sub_nca_result; | ||
| 632 | } | ||
| 633 | continue; | ||
| 634 | } | ||
| 635 | const auto nca_result = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); | ||
| 636 | if (nca_result != InstallResult::Success) { | ||
| 637 | return nca_result; | ||
| 627 | } | 638 | } |
| 628 | } | 639 | } |
| 629 | 640 | ||
| @@ -662,7 +673,34 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, | |||
| 662 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); | 673 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); |
| 663 | } | 674 | } |
| 664 | 675 | ||
| 676 | InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header, | ||
| 677 | const ContentRecord& base_record, | ||
| 678 | bool overwrite_if_exists, const VfsCopyFunction& copy) { | ||
| 679 | const CNMTHeader header{ | ||
| 680 | .title_id = nca.GetTitleId(), | ||
| 681 | .title_version = base_header.title_version, | ||
| 682 | .type = base_header.type, | ||
| 683 | .reserved = {}, | ||
| 684 | .table_offset = 0x10, | ||
| 685 | .number_content_entries = 1, | ||
| 686 | .number_meta_entries = 0, | ||
| 687 | .attributes = 0, | ||
| 688 | .reserved2 = {}, | ||
| 689 | .is_committed = 0, | ||
| 690 | .required_download_system_version = 0, | ||
| 691 | .reserved3 = {}, | ||
| 692 | }; | ||
| 693 | const OptionalHeader opt_header{0, 0}; | ||
| 694 | const CNMT new_cnmt(header, opt_header, {base_record}, {}); | ||
| 695 | if (!RawInstallYuzuMeta(new_cnmt)) { | ||
| 696 | return InstallResult::ErrorMetaFailed; | ||
| 697 | } | ||
| 698 | return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id); | ||
| 699 | } | ||
| 700 | |||
| 665 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | 701 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { |
| 702 | bool removed_data = false; | ||
| 703 | |||
| 666 | const auto delete_nca = [this](const NcaID& id) { | 704 | const auto delete_nca = [this](const NcaID& id) { |
| 667 | const auto path = GetRelativePathFromNcaID(id, false, true, false); | 705 | const auto path = GetRelativePathFromNcaID(id, false, true, false); |
| 668 | 706 | ||
| @@ -706,11 +744,20 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | |||
| 706 | const auto deleted_html = delete_nca(html_id); | 744 | const auto deleted_html = delete_nca(html_id); |
| 707 | const auto deleted_legal = delete_nca(legal_id); | 745 | const auto deleted_legal = delete_nca(legal_id); |
| 708 | 746 | ||
| 709 | return deleted_meta && (deleted_meta || deleted_program || deleted_data || | 747 | removed_data |= (deleted_meta || deleted_program || deleted_data || deleted_control || |
| 710 | deleted_control || deleted_html || deleted_legal); | 748 | deleted_html || deleted_legal); |
| 711 | } | 749 | } |
| 712 | 750 | ||
| 713 | return false; | 751 | // If patch entries for any program exist in yuzu meta, remove them |
| 752 | for (u8 i = 0; i < 0x10; i++) { | ||
| 753 | const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); | ||
| 754 | const auto filename = GetCNMTName(TitleType::Update, title_id + i); | ||
| 755 | if (meta_dir->GetFile(filename)) { | ||
| 756 | removed_data |= meta_dir->DeleteFile(filename); | ||
| 757 | } | ||
| 758 | } | ||
| 759 | |||
| 760 | return removed_data; | ||
| 714 | } | 761 | } |
| 715 | 762 | ||
| 716 | InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, | 763 | InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, |
| @@ -964,7 +1011,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord | |||
| 964 | const auto res = GetEntryRaw(title_id, type); | 1011 | const auto res = GetEntryRaw(title_id, type); |
| 965 | if (res == nullptr) | 1012 | if (res == nullptr) |
| 966 | return nullptr; | 1013 | return nullptr; |
| 967 | return std::make_unique<NCA>(res, nullptr, 0); | 1014 | return std::make_unique<NCA>(res); |
| 968 | } | 1015 | } |
| 969 | 1016 | ||
| 970 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( | 1017 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( |
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index bd7f53eaf..64815a845 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h | |||
| @@ -24,6 +24,7 @@ enum class NCAContentType : u8; | |||
| 24 | enum class TitleType : u8; | 24 | enum class TitleType : u8; |
| 25 | 25 | ||
| 26 | struct ContentRecord; | 26 | struct ContentRecord; |
| 27 | struct CNMTHeader; | ||
| 27 | struct MetaRecord; | 28 | struct MetaRecord; |
| 28 | class RegisteredCache; | 29 | class RegisteredCache; |
| 29 | 30 | ||
| @@ -169,6 +170,10 @@ public: | |||
| 169 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, | 170 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, |
| 170 | const VfsCopyFunction& copy = &VfsRawCopy); | 171 | const VfsCopyFunction& copy = &VfsRawCopy); |
| 171 | 172 | ||
| 173 | InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header, | ||
| 174 | const ContentRecord& base_record, bool overwrite_if_exists = false, | ||
| 175 | const VfsCopyFunction& copy = &VfsRawCopy); | ||
| 176 | |||
| 172 | // Removes an existing entry based on title id | 177 | // Removes an existing entry based on title id |
| 173 | bool RemoveExistingEntry(u64 title_id) const; | 178 | bool RemoveExistingEntry(u64 title_id) const; |
| 174 | 179 | ||
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index aa4726cfa..1bc07dae5 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp | |||
| @@ -26,13 +26,12 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi | |||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | updatable = app_loader.IsRomFSUpdatable(); | 28 | updatable = app_loader.IsRomFSUpdatable(); |
| 29 | ivfc_offset = app_loader.ReadRomFSIVFCOffset(); | ||
| 30 | } | 29 | } |
| 31 | 30 | ||
| 32 | RomFSFactory::~RomFSFactory() = default; | 31 | RomFSFactory::~RomFSFactory() = default; |
| 33 | 32 | ||
| 34 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { | 33 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { |
| 35 | update_raw = std::move(update_raw_file); | 34 | packed_update_raw = std::move(update_raw_file); |
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { | 37 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { |
| @@ -40,9 +39,11 @@ VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const | |||
| 40 | return file; | 39 | return file; |
| 41 | } | 40 | } |
| 42 | 41 | ||
| 42 | const auto type = ContentRecordType::Program; | ||
| 43 | const auto nca = content_provider.GetEntry(current_process_title_id, type); | ||
| 43 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, | 44 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, |
| 44 | content_provider}; | 45 | content_provider}; |
| 45 | return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); | 46 | return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw); |
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { | 49 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { |
| @@ -54,7 +55,7 @@ VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) | |||
| 54 | 55 | ||
| 55 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; | 56 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; |
| 56 | 57 | ||
| 57 | return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); | 58 | return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type); |
| 58 | } | 59 | } |
| 59 | 60 | ||
| 60 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 61 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 7ec40d19d..e4809bc94 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h | |||
| @@ -40,21 +40,22 @@ public: | |||
| 40 | Service::FileSystem::FileSystemController& controller); | 40 | Service::FileSystem::FileSystemController& controller); |
| 41 | ~RomFSFactory(); | 41 | ~RomFSFactory(); |
| 42 | 42 | ||
| 43 | void SetPackedUpdate(VirtualFile update_raw_file); | 43 | void SetPackedUpdate(VirtualFile packed_update_raw); |
| 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; | 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; |
| 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; | 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; |
| 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
| 47 | ContentRecordType type) const; | 47 | ContentRecordType type) const; |
| 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; | 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; |
| 49 | |||
| 50 | private: | ||
| 51 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, | 49 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, |
| 52 | ContentRecordType type) const; | 50 | ContentRecordType type) const; |
| 53 | 51 | ||
| 52 | private: | ||
| 54 | VirtualFile file; | 53 | VirtualFile file; |
| 55 | VirtualFile update_raw; | 54 | VirtualFile packed_update_raw; |
| 55 | |||
| 56 | VirtualFile base; | ||
| 57 | |||
| 56 | bool updatable; | 58 | bool updatable; |
| 57 | u64 ivfc_offset; | ||
| 58 | 59 | ||
| 59 | ContentProvider& content_provider; | 60 | ContentProvider& content_provider; |
| 60 | Service::FileSystem::FileSystemController& filesystem_controller; | 61 | Service::FileSystem::FileSystemController& filesystem_controller; |
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c90e6e372..68e8ec22f 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp | |||
| @@ -164,24 +164,6 @@ VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType titl | |||
| 164 | return nullptr; | 164 | return nullptr; |
| 165 | } | 165 | } |
| 166 | 166 | ||
| 167 | std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const { | ||
| 168 | if (extracted) | ||
| 169 | LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||
| 170 | std::vector<Core::Crypto::Key128> out; | ||
| 171 | for (const auto& ticket_file : ticket_files) { | ||
| 172 | if (ticket_file == nullptr || | ||
| 173 | ticket_file->GetSize() < | ||
| 174 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | ||
| 175 | continue; | ||
| 176 | } | ||
| 177 | |||
| 178 | out.emplace_back(); | ||
| 179 | ticket_file->Read(out.back().data(), out.back().size(), | ||
| 180 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||
| 181 | } | ||
| 182 | return out; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::vector<VirtualFile> NSP::GetFiles() const { | 167 | std::vector<VirtualFile> NSP::GetFiles() const { |
| 186 | return pfs->GetFiles(); | 168 | return pfs->GetFiles(); |
| 187 | } | 169 | } |
| @@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) { | |||
| 208 | continue; | 190 | continue; |
| 209 | } | 191 | } |
| 210 | 192 | ||
| 211 | if (ticket_file->GetSize() < | 193 | auto ticket = Core::Crypto::Ticket::Read(ticket_file); |
| 212 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | 194 | if (!keys.AddTicket(ticket)) { |
| 195 | LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName()); | ||
| 213 | continue; | 196 | continue; |
| 214 | } | 197 | } |
| 215 | |||
| 216 | Core::Crypto::Key128 key{}; | ||
| 217 | ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||
| 218 | |||
| 219 | // We get the name without the extension in order to create the rights ID. | ||
| 220 | std::string name_only(ticket_file->GetName()); | ||
| 221 | name_only.erase(name_only.size() - 4); | ||
| 222 | |||
| 223 | const auto rights_id_raw = Common::HexStringToArray<16>(name_only); | ||
| 224 | u128 rights_id; | ||
| 225 | std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); | ||
| 226 | keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 227 | } | 198 | } |
| 228 | } | 199 | } |
| 229 | 200 | ||
| @@ -249,7 +220,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { | |||
| 249 | } | 220 | } |
| 250 | 221 | ||
| 251 | const auto nca = std::make_shared<NCA>(outer_file); | 222 | const auto nca = std::make_shared<NCA>(outer_file); |
| 252 | if (nca->GetStatus() != Loader::ResultStatus::Success) { | 223 | if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) { |
| 253 | program_status[nca->GetTitleId()] = nca->GetStatus(); | 224 | program_status[nca->GetTitleId()] = nca->GetStatus(); |
| 254 | continue; | 225 | continue; |
| 255 | } | 226 | } |
| @@ -280,7 +251,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { | |||
| 280 | continue; | 251 | continue; |
| 281 | } | 252 | } |
| 282 | 253 | ||
| 283 | auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); | 254 | auto next_nca = std::make_shared<NCA>(std::move(next_file)); |
| 284 | 255 | ||
| 285 | if (next_nca->GetType() == NCAContentType::Program) { | 256 | if (next_nca->GetType() == NCAContentType::Program) { |
| 286 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); | 257 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); |
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 27f97c725..915bffca9 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h | |||
| @@ -53,7 +53,6 @@ public: | |||
| 53 | TitleType title_type = TitleType::Application) const; | 53 | TitleType title_type = TitleType::Application) const; |
| 54 | VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, | 54 | VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, |
| 55 | TitleType title_type = TitleType::Application) const; | 55 | TitleType title_type = TitleType::Application) const; |
| 56 | std::vector<Core::Crypto::Key128> GetTitlekey() const; | ||
| 57 | 56 | ||
| 58 | std::vector<VirtualFile> GetFiles() const override; | 57 | std::vector<VirtualFile> GetFiles() const override; |
| 59 | 58 | ||
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 3300d4f79..27755cb58 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #include "common/assert.h" | 4 | #include "common/assert.h" |
| 5 | #include "common/logging/log.h" | 5 | #include "common/logging/log.h" |
| 6 | #include "common/settings.h" | ||
| 7 | #include "common/settings_enums.h" | ||
| 6 | #include "core/frontend/applets/controller.h" | 8 | #include "core/frontend/applets/controller.h" |
| 7 | #include "core/hid/emulated_controller.h" | 9 | #include "core/hid/emulated_controller.h" |
| 8 | #include "core/hid/hid_core.h" | 10 | #include "core/hid/hid_core.h" |
| @@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac | |||
| 62 | controller->Connect(true); | 64 | controller->Connect(true); |
| 63 | } | 65 | } |
| 64 | } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && | 66 | } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && |
| 65 | !Settings::values.use_docked_mode.GetValue()) { | 67 | !Settings::IsDockedMode()) { |
| 66 | // We should *never* reach here under any normal circumstances. | 68 | // We should *never* reach here under any normal circumstances. |
| 67 | controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); | 69 | controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); |
| 68 | controller->Connect(true); | 70 | controller->Connect(true); |
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index b4081fc39..2590b20da 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 7 | #include "common/settings.h" | 7 | #include "common/settings.h" |
| 8 | #include "common/settings_enums.h" | ||
| 8 | #include "core/frontend/framebuffer_layout.h" | 9 | #include "core/frontend/framebuffer_layout.h" |
| 9 | 10 | ||
| 10 | namespace Layout { | 11 | namespace Layout { |
| @@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { | |||
| 49 | } | 50 | } |
| 50 | 51 | ||
| 51 | FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { | 52 | FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { |
| 52 | const bool is_docked = Settings::values.use_docked_mode.GetValue(); | 53 | const bool is_docked = Settings::IsDockedMode(); |
| 53 | const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width; | 54 | const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width; |
| 54 | const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height; | 55 | const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height; |
| 55 | 56 | ||
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp index 7d6373414..cf53c04d9 100644 --- a/src/core/hid/hid_core.cpp +++ b/src/core/hid/hid_core.cpp | |||
| @@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const { | |||
| 154 | return NpadIdType::Player1; | 154 | return NpadIdType::Player1; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | void HIDCore::SetLastActiveController(NpadIdType npad_id) { | ||
| 158 | last_active_controller = npad_id; | ||
| 159 | } | ||
| 160 | |||
| 161 | NpadIdType HIDCore::GetLastActiveController() const { | ||
| 162 | return last_active_controller; | ||
| 163 | } | ||
| 164 | |||
| 157 | void HIDCore::EnableAllControllerConfiguration() { | 165 | void HIDCore::EnableAllControllerConfiguration() { |
| 158 | player_1->EnableConfiguration(); | 166 | player_1->EnableConfiguration(); |
| 159 | player_2->EnableConfiguration(); | 167 | player_2->EnableConfiguration(); |
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h index 5fe36551e..80abab18b 100644 --- a/src/core/hid/hid_core.h +++ b/src/core/hid/hid_core.h | |||
| @@ -48,6 +48,12 @@ public: | |||
| 48 | /// Returns the first disconnected npad id | 48 | /// Returns the first disconnected npad id |
| 49 | NpadIdType GetFirstDisconnectedNpadId() const; | 49 | NpadIdType GetFirstDisconnectedNpadId() const; |
| 50 | 50 | ||
| 51 | /// Sets the npad id of the last active controller | ||
| 52 | void SetLastActiveController(NpadIdType npad_id); | ||
| 53 | |||
| 54 | /// Returns the npad id of the last controller that pushed a button | ||
| 55 | NpadIdType GetLastActiveController() const; | ||
| 56 | |||
| 51 | /// Sets all emulated controllers into configuring mode. | 57 | /// Sets all emulated controllers into configuring mode. |
| 52 | void EnableAllControllerConfiguration(); | 58 | void EnableAllControllerConfiguration(); |
| 53 | 59 | ||
| @@ -77,6 +83,7 @@ private: | |||
| 77 | std::unique_ptr<EmulatedConsole> console; | 83 | std::unique_ptr<EmulatedConsole> console; |
| 78 | std::unique_ptr<EmulatedDevices> devices; | 84 | std::unique_ptr<EmulatedDevices> devices; |
| 79 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; | 85 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; |
| 86 | NpadIdType last_active_controller{NpadIdType::Handheld}; | ||
| 80 | }; | 87 | }; |
| 81 | 88 | ||
| 82 | } // namespace Core::HID | 89 | } // namespace Core::HID |
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 6b35f448c..00beb40dd 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h | |||
| @@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 { | |||
| 289 | Tight = 2, | 289 | Tight = 2, |
| 290 | }; | 290 | }; |
| 291 | 291 | ||
| 292 | // This is nn::settings::system::TouchScreenMode | ||
| 293 | enum class TouchScreenMode : u32 { | ||
| 294 | Stylus = 0, | ||
| 295 | Standard = 1, | ||
| 296 | }; | ||
| 297 | |||
| 298 | // This is nn::hid::TouchScreenModeForNx | ||
| 299 | enum class TouchScreenModeForNx : u8 { | ||
| 300 | UseSystemSetting, | ||
| 301 | Finger, | ||
| 302 | Heat2, | ||
| 303 | }; | ||
| 304 | |||
| 292 | // This is nn::hid::NpadStyleTag | 305 | // This is nn::hid::NpadStyleTag |
| 293 | struct NpadStyleTag { | 306 | struct NpadStyleTag { |
| 294 | union { | 307 | union { |
| @@ -334,6 +347,14 @@ struct TouchState { | |||
| 334 | }; | 347 | }; |
| 335 | static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); | 348 | static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); |
| 336 | 349 | ||
| 350 | // This is nn::hid::TouchScreenConfigurationForNx | ||
| 351 | struct TouchScreenConfigurationForNx { | ||
| 352 | TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; | ||
| 353 | INSERT_PADDING_BYTES(0xF); | ||
| 354 | }; | ||
| 355 | static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10, | ||
| 356 | "TouchScreenConfigurationForNx is an invalid size"); | ||
| 357 | |||
| 337 | struct NpadColor { | 358 | struct NpadColor { |
| 338 | u8 r{}; | 359 | u8 r{}; |
| 339 | u8 g{}; | 360 | u8 g{}; |
| @@ -662,6 +683,11 @@ struct MouseState { | |||
| 662 | }; | 683 | }; |
| 663 | static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); | 684 | static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); |
| 664 | 685 | ||
| 686 | struct UniquePadId { | ||
| 687 | u64 id; | ||
| 688 | }; | ||
| 689 | static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size"); | ||
| 690 | |||
| 665 | /// Converts a NpadIdType to an array index. | 691 | /// Converts a NpadIdType to an array index. |
| 666 | constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { | 692 | constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { |
| 667 | switch (npad_id_type) { | 693 | switch (npad_id_type) { |
diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp index 90e4e8fb0..e7da7a21d 100644 --- a/src/core/hle/kernel/k_capabilities.cpp +++ b/src/core/hle/kernel/k_capabilities.cpp | |||
| @@ -156,7 +156,6 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) { | |||
| 156 | const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; | 156 | const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; |
| 157 | const size_t num_pages = 1; | 157 | const size_t num_pages = 1; |
| 158 | const size_t size = num_pages * PageSize; | 158 | const size_t size = num_pages * PageSize; |
| 159 | R_UNLESS(num_pages != 0, ResultInvalidSize); | ||
| 160 | R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); | 159 | R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); |
| 161 | R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); | 160 | R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); |
| 162 | 161 | ||
diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h index 00bef6ea1..27f43cd19 100644 --- a/src/core/hle/kernel/k_hardware_timer.h +++ b/src/core/hle/kernel/k_hardware_timer.h | |||
| @@ -19,13 +19,7 @@ public: | |||
| 19 | void Initialize(); | 19 | void Initialize(); |
| 20 | void Finalize(); | 20 | void Finalize(); |
| 21 | 21 | ||
| 22 | s64 GetCount() const { | 22 | s64 GetTick() const; |
| 23 | return GetTick(); | ||
| 24 | } | ||
| 25 | |||
| 26 | void RegisterTask(KTimerTask* task, s64 time_from_now) { | ||
| 27 | this->RegisterAbsoluteTask(task, GetTick() + time_from_now); | ||
| 28 | } | ||
| 29 | 23 | ||
| 30 | void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { | 24 | void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { |
| 31 | KScopedDisableDispatch dd{m_kernel}; | 25 | KScopedDisableDispatch dd{m_kernel}; |
| @@ -42,7 +36,6 @@ private: | |||
| 42 | void EnableInterrupt(s64 wakeup_time); | 36 | void EnableInterrupt(s64 wakeup_time); |
| 43 | void DisableInterrupt(); | 37 | void DisableInterrupt(); |
| 44 | bool GetInterruptEnabled(); | 38 | bool GetInterruptEnabled(); |
| 45 | s64 GetTick() const; | ||
| 46 | void DoTask(); | 39 | void DoTask(); |
| 47 | 40 | ||
| 48 | private: | 41 | private: |
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index e573e2a57..4a099286b 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp | |||
| @@ -38,7 +38,7 @@ namespace { | |||
| 38 | */ | 38 | */ |
| 39 | void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, | 39 | void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, |
| 40 | KProcessAddress stack_top) { | 40 | KProcessAddress stack_top) { |
| 41 | const KProcessAddress entry_point = owner_process.GetPageTable().GetCodeRegionStart(); | 41 | const KProcessAddress entry_point = owner_process.GetEntryPoint(); |
| 42 | ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); | 42 | ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); |
| 43 | 43 | ||
| 44 | KThread* thread = KThread::Create(system.Kernel()); | 44 | KThread* thread = KThread::Create(system.Kernel()); |
| @@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string | |||
| 96 | process->m_is_suspended = false; | 96 | process->m_is_suspended = false; |
| 97 | process->m_schedule_count = 0; | 97 | process->m_schedule_count = 0; |
| 98 | process->m_is_handle_table_initialized = false; | 98 | process->m_is_handle_table_initialized = false; |
| 99 | process->m_is_hbl = false; | ||
| 99 | 100 | ||
| 100 | // Open a reference to the resource limit. | 101 | // Open a reference to the resource limit. |
| 101 | process->m_resource_limit->Open(); | 102 | process->m_resource_limit->Open(); |
| @@ -351,12 +352,29 @@ Result KProcess::SetActivity(ProcessActivity activity) { | |||
| 351 | R_SUCCEED(); | 352 | R_SUCCEED(); |
| 352 | } | 353 | } |
| 353 | 354 | ||
| 354 | Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { | 355 | Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, |
| 356 | bool is_hbl) { | ||
| 355 | m_program_id = metadata.GetTitleID(); | 357 | m_program_id = metadata.GetTitleID(); |
| 356 | m_ideal_core = metadata.GetMainThreadCore(); | 358 | m_ideal_core = metadata.GetMainThreadCore(); |
| 357 | m_is_64bit_process = metadata.Is64BitProgram(); | 359 | m_is_64bit_process = metadata.Is64BitProgram(); |
| 358 | m_system_resource_size = metadata.GetSystemResourceSize(); | 360 | m_system_resource_size = metadata.GetSystemResourceSize(); |
| 359 | m_image_size = code_size; | 361 | m_image_size = code_size; |
| 362 | m_is_hbl = is_hbl; | ||
| 363 | |||
| 364 | if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { | ||
| 365 | // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. | ||
| 366 | // However, some (buggy) programs/libraries like skyline incorrectly depend on the | ||
| 367 | // existence of ASLR pages before the entry point, so we will adjust the load address | ||
| 368 | // to point to about 2GiB into the ASLR region. | ||
| 369 | m_code_address = 0x8000'0000; | ||
| 370 | } else { | ||
| 371 | // All other processes can be mapped at the beginning of the code region. | ||
| 372 | if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) { | ||
| 373 | m_code_address = 0x800'0000; | ||
| 374 | } else { | ||
| 375 | m_code_address = 0x20'0000; | ||
| 376 | } | ||
| 377 | } | ||
| 360 | 378 | ||
| 361 | KScopedResourceReservation memory_reservation( | 379 | KScopedResourceReservation memory_reservation( |
| 362 | m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); | 380 | m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); |
| @@ -368,15 +386,15 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: | |||
| 368 | // Initialize process address space | 386 | // Initialize process address space |
| 369 | if (const Result result{m_page_table.InitializeForProcess( | 387 | if (const Result result{m_page_table.InitializeForProcess( |
| 370 | metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, | 388 | metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, |
| 371 | 0x8000000, code_size, std::addressof(m_kernel.GetAppSystemResource()), m_resource_limit, | 389 | this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()), |
| 372 | m_kernel.System().ApplicationMemory())}; | 390 | m_resource_limit, m_kernel.System().ApplicationMemory())}; |
| 373 | result.IsError()) { | 391 | result.IsError()) { |
| 374 | R_RETURN(result); | 392 | R_RETURN(result); |
| 375 | } | 393 | } |
| 376 | 394 | ||
| 377 | // Map process code region | 395 | // Map process code region |
| 378 | if (const Result result{m_page_table.MapProcessCode(m_page_table.GetCodeRegionStart(), | 396 | if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize, |
| 379 | code_size / PageSize, KMemoryState::Code, | 397 | KMemoryState::Code, |
| 380 | KMemoryPermission::None)}; | 398 | KMemoryPermission::None)}; |
| 381 | result.IsError()) { | 399 | result.IsError()) { |
| 382 | R_RETURN(result); | 400 | R_RETURN(result); |
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index c9b37e138..146e07a57 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h | |||
| @@ -177,6 +177,10 @@ public: | |||
| 177 | return m_program_id; | 177 | return m_program_id; |
| 178 | } | 178 | } |
| 179 | 179 | ||
| 180 | KProcessAddress GetEntryPoint() const { | ||
| 181 | return m_code_address; | ||
| 182 | } | ||
| 183 | |||
| 180 | /// Gets the resource limit descriptor for this process | 184 | /// Gets the resource limit descriptor for this process |
| 181 | KResourceLimit* GetResourceLimit() const; | 185 | KResourceLimit* GetResourceLimit() const; |
| 182 | 186 | ||
| @@ -334,7 +338,8 @@ public: | |||
| 334 | * @returns ResultSuccess if all relevant metadata was able to be | 338 | * @returns ResultSuccess if all relevant metadata was able to be |
| 335 | * loaded and parsed. Otherwise, an error code is returned. | 339 | * loaded and parsed. Otherwise, an error code is returned. |
| 336 | */ | 340 | */ |
| 337 | Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); | 341 | Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, |
| 342 | bool is_hbl); | ||
| 338 | 343 | ||
| 339 | /** | 344 | /** |
| 340 | * Starts the main application thread for this process. | 345 | * Starts the main application thread for this process. |
| @@ -364,6 +369,10 @@ public: | |||
| 364 | return GetProcessId(); | 369 | return GetProcessId(); |
| 365 | } | 370 | } |
| 366 | 371 | ||
| 372 | bool IsHbl() const { | ||
| 373 | return m_is_hbl; | ||
| 374 | } | ||
| 375 | |||
| 367 | bool IsSignaled() const override; | 376 | bool IsSignaled() const override; |
| 368 | 377 | ||
| 369 | void DoWorkerTaskImpl(); | 378 | void DoWorkerTaskImpl(); |
| @@ -485,6 +494,9 @@ private: | |||
| 485 | /// Address indicating the location of the process' dedicated TLS region. | 494 | /// Address indicating the location of the process' dedicated TLS region. |
| 486 | KProcessAddress m_plr_address = 0; | 495 | KProcessAddress m_plr_address = 0; |
| 487 | 496 | ||
| 497 | /// Address indicating the location of the process's entry point. | ||
| 498 | KProcessAddress m_code_address = 0; | ||
| 499 | |||
| 488 | /// Random values for svcGetInfo RandomEntropy | 500 | /// Random values for svcGetInfo RandomEntropy |
| 489 | std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; | 501 | std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; |
| 490 | 502 | ||
| @@ -518,6 +530,7 @@ private: | |||
| 518 | bool m_is_immortal{}; | 530 | bool m_is_immortal{}; |
| 519 | bool m_is_handle_table_initialized{}; | 531 | bool m_is_handle_table_initialized{}; |
| 520 | bool m_is_initialized{}; | 532 | bool m_is_initialized{}; |
| 533 | bool m_is_hbl{}; | ||
| 521 | 534 | ||
| 522 | std::atomic<u16> m_num_running_threads{}; | 535 | std::atomic<u16> m_num_running_threads{}; |
| 523 | 536 | ||
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index fcee26a29..d8a63aaf8 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "common/overflow.h" | 5 | #include "common/overflow.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/core_timing.h" | 7 | #include "core/core_timing.h" |
| 8 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 8 | #include "core/hle/kernel/k_resource_limit.h" | 9 | #include "core/hle/kernel/k_resource_limit.h" |
| 9 | #include "core/hle/kernel/svc_results.h" | 10 | #include "core/hle/kernel/svc_results.h" |
| 10 | 11 | ||
| @@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel) | |||
| 15 | : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} | 16 | : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} |
| 16 | KResourceLimit::~KResourceLimit() = default; | 17 | KResourceLimit::~KResourceLimit() = default; |
| 17 | 18 | ||
| 18 | void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { | 19 | void KResourceLimit::Initialize() {} |
| 19 | m_core_timing = core_timing; | ||
| 20 | } | ||
| 21 | 20 | ||
| 22 | void KResourceLimit::Finalize() {} | 21 | void KResourceLimit::Finalize() {} |
| 23 | 22 | ||
| @@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { | |||
| 86 | } | 85 | } |
| 87 | 86 | ||
| 88 | bool KResourceLimit::Reserve(LimitableResource which, s64 value) { | 87 | bool KResourceLimit::Reserve(LimitableResource which, s64 value) { |
| 89 | return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout); | 88 | return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout); |
| 90 | } | 89 | } |
| 91 | 90 | ||
| 92 | bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | 91 | bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { |
| @@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | |||
| 117 | } | 116 | } |
| 118 | 117 | ||
| 119 | if (m_current_hints[index] + value <= m_limit_values[index] && | 118 | if (m_current_hints[index] + value <= m_limit_values[index] && |
| 120 | (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) { | 119 | (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) { |
| 121 | m_waiter_count++; | 120 | m_waiter_count++; |
| 122 | m_cond_var.Wait(std::addressof(m_lock), timeout, false); | 121 | m_cond_var.Wait(std::addressof(m_lock), timeout, false); |
| 123 | m_waiter_count--; | 122 | m_waiter_count--; |
| @@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { | |||
| 154 | 153 | ||
| 155 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { | 154 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { |
| 156 | auto* resource_limit = KResourceLimit::Create(system.Kernel()); | 155 | auto* resource_limit = KResourceLimit::Create(system.Kernel()); |
| 157 | resource_limit->Initialize(std::addressof(system.CoreTiming())); | 156 | resource_limit->Initialize(); |
| 158 | 157 | ||
| 159 | // Initialize default resource limit values. | 158 | // Initialize default resource limit values. |
| 160 | // TODO(bunnei): These values are the system defaults, the limits for service processes are | 159 | // TODO(bunnei): These values are the system defaults, the limits for service processes are |
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h index 15e69af56..b733ec8f8 100644 --- a/src/core/hle/kernel/k_resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h | |||
| @@ -31,7 +31,7 @@ public: | |||
| 31 | explicit KResourceLimit(KernelCore& kernel); | 31 | explicit KResourceLimit(KernelCore& kernel); |
| 32 | ~KResourceLimit() override; | 32 | ~KResourceLimit() override; |
| 33 | 33 | ||
| 34 | void Initialize(const Core::Timing::CoreTiming* core_timing); | 34 | void Initialize(); |
| 35 | void Finalize() override; | 35 | void Finalize() override; |
| 36 | 36 | ||
| 37 | s64 GetLimitValue(LimitableResource which) const; | 37 | s64 GetLimitValue(LimitableResource which) const; |
| @@ -57,7 +57,6 @@ private: | |||
| 57 | mutable KLightLock m_lock; | 57 | mutable KLightLock m_lock; |
| 58 | s32 m_waiter_count{}; | 58 | s32 m_waiter_count{}; |
| 59 | KLightConditionVariable m_cond_var; | 59 | KLightConditionVariable m_cond_var; |
| 60 | const Core::Timing::CoreTiming* m_core_timing{}; | ||
| 61 | }; | 60 | }; |
| 62 | 61 | ||
| 63 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); | 62 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); |
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h index c485022f5..b62415da7 100644 --- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h | |||
| @@ -28,7 +28,7 @@ public: | |||
| 28 | ~KScopedSchedulerLockAndSleep() { | 28 | ~KScopedSchedulerLockAndSleep() { |
| 29 | // Register the sleep. | 29 | // Register the sleep. |
| 30 | if (m_timeout_tick > 0) { | 30 | if (m_timeout_tick > 0) { |
| 31 | m_timer->RegisterTask(m_thread, m_timeout_tick); | 31 | m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | // Unlock the scheduler. | 34 | // Unlock the scheduler. |
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index ebe7582c6..a1134b7e2 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -231,7 +231,7 @@ struct KernelCore::Impl { | |||
| 231 | void InitializeSystemResourceLimit(KernelCore& kernel, | 231 | void InitializeSystemResourceLimit(KernelCore& kernel, |
| 232 | const Core::Timing::CoreTiming& core_timing) { | 232 | const Core::Timing::CoreTiming& core_timing) { |
| 233 | system_resource_limit = KResourceLimit::Create(system.Kernel()); | 233 | system_resource_limit = KResourceLimit::Create(system.Kernel()); |
| 234 | system_resource_limit->Initialize(&core_timing); | 234 | system_resource_limit->Initialize(); |
| 235 | KResourceLimit::Register(kernel, system_resource_limit); | 235 | KResourceLimit::Register(kernel, system_resource_limit); |
| 236 | 236 | ||
| 237 | const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; | 237 | const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; |
diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp index 04cc5ea64..90ee43521 100644 --- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 5 | #include "core/hle/kernel/k_memory_layout.h" | 6 | #include "core/hle/kernel/k_memory_layout.h" |
| 6 | #include "core/hle/kernel/k_process.h" | 7 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/kernel.h" | 8 | #include "core/hle/kernel/kernel.h" |
| @@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ | |||
| 52 | if (timeout_ns > 0) { | 53 | if (timeout_ns > 0) { |
| 53 | const s64 offset_tick(timeout_ns); | 54 | const s64 offset_tick(timeout_ns); |
| 54 | if (offset_tick > 0) { | 55 | if (offset_tick > 0) { |
| 55 | timeout = offset_tick + 2; | 56 | timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; |
| 56 | if (timeout <= 0) { | 57 | if (timeout <= 0) { |
| 57 | timeout = std::numeric_limits<s64>::max(); | 58 | timeout = std::numeric_limits<s64>::max(); |
| 58 | } | 59 | } |
diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp index ca120d67e..bb678e6c5 100644 --- a/src/core/hle/kernel/svc/svc_condition_variable.cpp +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 5 | #include "core/hle/kernel/k_memory_layout.h" | 6 | #include "core/hle/kernel/k_memory_layout.h" |
| 6 | #include "core/hle/kernel/k_process.h" | 7 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/kernel.h" | 8 | #include "core/hle/kernel/kernel.h" |
| @@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u | |||
| 25 | if (timeout_ns > 0) { | 26 | if (timeout_ns > 0) { |
| 26 | const s64 offset_tick(timeout_ns); | 27 | const s64 offset_tick(timeout_ns); |
| 27 | if (offset_tick > 0) { | 28 | if (offset_tick > 0) { |
| 28 | timeout = offset_tick + 2; | 29 | timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; |
| 29 | if (timeout <= 0) { | 30 | if (timeout <= 0) { |
| 30 | timeout = std::numeric_limits<s64>::max(); | 31 | timeout = std::numeric_limits<s64>::max(); |
| 31 | } | 32 | } |
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp index 4c14ce668..00b65429b 100644 --- a/src/core/hle/kernel/svc/svc_debug_string.cpp +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp | |||
| @@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) { | |||
| 14 | 14 | ||
| 15 | std::string str(len, '\0'); | 15 | std::string str(len, '\0'); |
| 16 | GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); | 16 | GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); |
| 17 | LOG_DEBUG(Debug_Emulated, "{}", str); | 17 | LOG_INFO(Debug_Emulated, "{}", str); |
| 18 | 18 | ||
| 19 | R_SUCCEED(); | 19 | R_SUCCEED(); |
| 20 | } | 20 | } |
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp index 580cf2f75..c581c086b 100644 --- a/src/core/hle/kernel/svc/svc_exception.cpp +++ b/src/core/hle/kernel/svc/svc_exception.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/debugger/debugger.h" | 5 | #include "core/debugger/debugger.h" |
| 6 | #include "core/hle/kernel/k_process.h" | ||
| 6 | #include "core/hle/kernel/k_thread.h" | 7 | #include "core/hle/kernel/k_thread.h" |
| 7 | #include "core/hle/kernel/svc.h" | 8 | #include "core/hle/kernel/svc.h" |
| 8 | #include "core/hle/kernel/svc_types.h" | 9 | #include "core/hle/kernel/svc_types.h" |
| @@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { | |||
| 107 | system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); | 108 | system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); |
| 108 | } | 109 | } |
| 109 | 110 | ||
| 110 | if (system.DebuggerEnabled()) { | 111 | const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl(); |
| 112 | const bool should_break = is_hbl || !notification_only; | ||
| 113 | |||
| 114 | if (system.DebuggerEnabled() && should_break) { | ||
| 111 | auto* thread = system.Kernel().GetCurrentEmuThread(); | 115 | auto* thread = system.Kernel().GetCurrentEmuThread(); |
| 112 | system.GetDebugger().NotifyThreadStopped(thread); | 116 | system.GetDebugger().NotifyThreadStopped(thread); |
| 113 | thread->RequestSuspend(Kernel::SuspendType::Debug); | 117 | thread->RequestSuspend(Kernel::SuspendType::Debug); |
diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp index 373ae7c8d..6b5e1cb8d 100644 --- a/src/core/hle/kernel/svc/svc_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_ipc.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "common/scratch_buffer.h" | 5 | #include "common/scratch_buffer.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/hle/kernel/k_client_session.h" | 7 | #include "core/hle/kernel/k_client_session.h" |
| 8 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 8 | #include "core/hle/kernel/k_process.h" | 9 | #include "core/hle/kernel/k_process.h" |
| 9 | #include "core/hle/kernel/k_server_session.h" | 10 | #include "core/hle/kernel/k_server_session.h" |
| 10 | #include "core/hle/kernel/svc.h" | 11 | #include "core/hle/kernel/svc.h" |
| @@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad | |||
| 82 | R_TRY(session->SendReply()); | 83 | R_TRY(session->SendReply()); |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 86 | // Convert the timeout from nanoseconds to ticks. | ||
| 87 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... | ||
| 88 | s64 timeout; | ||
| 89 | if (timeout_ns > 0) { | ||
| 90 | const s64 offset_tick(timeout_ns); | ||
| 91 | if (offset_tick > 0) { | ||
| 92 | timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; | ||
| 93 | if (timeout <= 0) { | ||
| 94 | timeout = std::numeric_limits<s64>::max(); | ||
| 95 | } | ||
| 96 | } else { | ||
| 97 | timeout = std::numeric_limits<s64>::max(); | ||
| 98 | } | ||
| 99 | } else { | ||
| 100 | timeout = timeout_ns; | ||
| 101 | } | ||
| 102 | |||
| 85 | // Wait for a message. | 103 | // Wait for a message. |
| 86 | while (true) { | 104 | while (true) { |
| 87 | // Wait for an object. | 105 | // Wait for an object. |
| 88 | s32 index; | 106 | s32 index; |
| 89 | Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), | 107 | Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), |
| 90 | num_handles, timeout_ns); | 108 | num_handles, timeout); |
| 91 | if (result == ResultTimedOut) { | 109 | if (result == ResultTimedOut) { |
| 92 | R_RETURN(result); | 110 | R_RETURN(result); |
| 93 | } | 111 | } |
diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp index 732bc017e..c8e820b6a 100644 --- a/src/core/hle/kernel/svc/svc_resource_limit.cpp +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp | |||
| @@ -21,7 +21,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) { | |||
| 21 | SCOPE_EXIT({ resource_limit->Close(); }); | 21 | SCOPE_EXIT({ resource_limit->Close(); }); |
| 22 | 22 | ||
| 23 | // Initialize the resource limit. | 23 | // Initialize the resource limit. |
| 24 | resource_limit->Initialize(std::addressof(system.CoreTiming())); | 24 | resource_limit->Initialize(); |
| 25 | 25 | ||
| 26 | // Register the limit. | 26 | // Register the limit. |
| 27 | KResourceLimit::Register(kernel, resource_limit); | 27 | KResourceLimit::Register(kernel, resource_limit); |
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index 366e8ed4a..8ebc1bd1c 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include "common/scope_exit.h" | 4 | #include "common/scope_exit.h" |
| 5 | #include "common/scratch_buffer.h" | 5 | #include "common/scratch_buffer.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 7 | #include "core/hle/kernel/k_process.h" | 8 | #include "core/hle/kernel/k_process.h" |
| 8 | #include "core/hle/kernel/k_readable_event.h" | 9 | #include "core/hle/kernel/k_readable_event.h" |
| 9 | #include "core/hle/kernel/svc.h" | 10 | #include "core/hle/kernel/svc.h" |
| @@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha | |||
| 83 | } | 84 | } |
| 84 | }); | 85 | }); |
| 85 | 86 | ||
| 87 | // Convert the timeout from nanoseconds to ticks. | ||
| 88 | s64 timeout; | ||
| 89 | if (timeout_ns > 0) { | ||
| 90 | u64 ticks = kernel.HardwareTimer().GetTick(); | ||
| 91 | ticks += timeout_ns; | ||
| 92 | ticks += 2; | ||
| 93 | |||
| 94 | timeout = ticks; | ||
| 95 | } else { | ||
| 96 | timeout = timeout_ns; | ||
| 97 | } | ||
| 98 | |||
| 86 | // Wait on the objects. | 99 | // Wait on the objects. |
| 87 | Result res = | 100 | Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); |
| 88 | KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns); | ||
| 89 | 101 | ||
| 90 | R_SUCCEED_IF(res == ResultSessionClosed); | 102 | R_SUCCEED_IF(res == ResultSessionClosed); |
| 91 | R_RETURN(res); | 103 | R_RETURN(res); |
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index 92bcea72b..933b82e30 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include "common/scope_exit.h" | 4 | #include "common/scope_exit.h" |
| 5 | #include "core/core.h" | 5 | #include "core/core.h" |
| 6 | #include "core/core_timing.h" | 6 | #include "core/core_timing.h" |
| 7 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 7 | #include "core/hle/kernel/k_process.h" | 8 | #include "core/hle/kernel/k_process.h" |
| 8 | #include "core/hle/kernel/k_scoped_resource_reservation.h" | 9 | #include "core/hle/kernel/k_scoped_resource_reservation.h" |
| 9 | #include "core/hle/kernel/k_thread.h" | 10 | #include "core/hle/kernel/k_thread.h" |
| @@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u | |||
| 42 | R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); | 43 | R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); |
| 43 | 44 | ||
| 44 | // Reserve a new thread from the process resource limit (waiting up to 100ms). | 45 | // Reserve a new thread from the process resource limit (waiting up to 100ms). |
| 45 | KScopedResourceReservation thread_reservation( | 46 | KScopedResourceReservation thread_reservation(std::addressof(process), |
| 46 | std::addressof(process), LimitableResource::ThreadCountMax, 1, | 47 | LimitableResource::ThreadCountMax, 1, |
| 47 | system.CoreTiming().GetGlobalTimeNs().count() + 100000000); | 48 | kernel.HardwareTimer().GetTick() + 100000000); |
| 48 | R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); | 49 | R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); |
| 49 | 50 | ||
| 50 | // Create the thread. | 51 | // Create the thread. |
| @@ -102,20 +103,31 @@ void ExitThread(Core::System& system) { | |||
| 102 | } | 103 | } |
| 103 | 104 | ||
| 104 | /// Sleep the current thread | 105 | /// Sleep the current thread |
| 105 | void SleepThread(Core::System& system, s64 nanoseconds) { | 106 | void SleepThread(Core::System& system, s64 ns) { |
| 106 | auto& kernel = system.Kernel(); | 107 | auto& kernel = system.Kernel(); |
| 107 | const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); | 108 | const auto yield_type = static_cast<Svc::YieldType>(ns); |
| 108 | 109 | ||
| 109 | LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); | 110 | LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns); |
| 110 | 111 | ||
| 111 | // When the input tick is positive, sleep. | 112 | // When the input tick is positive, sleep. |
| 112 | if (nanoseconds > 0) { | 113 | if (ns > 0) { |
| 113 | // Convert the timeout from nanoseconds to ticks. | 114 | // Convert the timeout from nanoseconds to ticks. |
| 114 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... | 115 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... |
| 116 | s64 timeout; | ||
| 117 | |||
| 118 | const s64 offset_tick(ns); | ||
| 119 | if (offset_tick > 0) { | ||
| 120 | timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; | ||
| 121 | if (timeout <= 0) { | ||
| 122 | timeout = std::numeric_limits<s64>::max(); | ||
| 123 | } | ||
| 124 | } else { | ||
| 125 | timeout = std::numeric_limits<s64>::max(); | ||
| 126 | } | ||
| 115 | 127 | ||
| 116 | // Sleep. | 128 | // Sleep. |
| 117 | // NOTE: Nintendo does not check the result of this sleep. | 129 | // NOTE: Nintendo does not check the result of this sleep. |
| 118 | static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); | 130 | static_cast<void>(GetCurrentThread(kernel).Sleep(timeout)); |
| 119 | } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { | 131 | } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { |
| 120 | KScheduler::YieldWithoutCoreMigration(kernel); | 132 | KScheduler::YieldWithoutCoreMigration(kernel); |
| 121 | } else if (yield_type == Svc::YieldType::WithCoreMigration) { | 133 | } else if (yield_type == Svc::YieldType::WithCoreMigration) { |
| @@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) { | |||
| 124 | KScheduler::YieldToAnyThread(kernel); | 136 | KScheduler::YieldToAnyThread(kernel); |
| 125 | } else { | 137 | } else { |
| 126 | // Nintendo does nothing at all if an otherwise invalid value is passed. | 138 | // Nintendo does nothing at all if an otherwise invalid value is passed. |
| 127 | ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); | ||
| 128 | } | 139 | } |
| 129 | } | 140 | } |
| 130 | 141 | ||
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 92a1439eb..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { | |||
| 62 | XCD = 108, | 62 | XCD = 108, |
| 63 | TMP451 = 109, | 63 | TMP451 = 109, |
| 64 | NIFM = 110, | 64 | NIFM = 110, |
| 65 | Hwopus = 111, | 65 | HwOpus = 111, |
| 66 | LSM6DS3 = 112, | 66 | LSM6DS3 = 112, |
| 67 | Bluetooth = 113, | 67 | Bluetooth = 113, |
| 68 | VI = 114, | 68 | VI = 114, |
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 8d057b3a8..8ffdd19e7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <cinttypes> | 6 | #include <cinttypes> |
| 7 | #include <cstring> | 7 | #include <cstring> |
| 8 | #include "common/settings.h" | 8 | #include "common/settings.h" |
| 9 | #include "common/settings_enums.h" | ||
| 9 | #include "core/core.h" | 10 | #include "core/core.h" |
| 10 | #include "core/file_sys/control_metadata.h" | 11 | #include "core/file_sys/control_metadata.h" |
| 11 | #include "core/file_sys/patch_manager.h" | 12 | #include "core/file_sys/patch_manager.h" |
| @@ -45,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3}; | |||
| 45 | constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; | 46 | constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; |
| 46 | 47 | ||
| 47 | enum class LaunchParameterKind : u32 { | 48 | enum class LaunchParameterKind : u32 { |
| 48 | ApplicationSpecific = 1, | 49 | UserChannel = 1, |
| 49 | AccountPreselectedUser = 2, | 50 | AccountPreselectedUser = 2, |
| 50 | }; | 51 | }; |
| 51 | 52 | ||
| @@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) { | |||
| 340 | void ISelfController::LockExit(HLERequestContext& ctx) { | 341 | void ISelfController::LockExit(HLERequestContext& ctx) { |
| 341 | LOG_DEBUG(Service_AM, "called"); | 342 | LOG_DEBUG(Service_AM, "called"); |
| 342 | 343 | ||
| 343 | system.SetExitLock(true); | 344 | system.SetExitLocked(true); |
| 344 | 345 | ||
| 345 | IPC::ResponseBuilder rb{ctx, 2}; | 346 | IPC::ResponseBuilder rb{ctx, 2}; |
| 346 | rb.Push(ResultSuccess); | 347 | rb.Push(ResultSuccess); |
| @@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) { | |||
| 349 | void ISelfController::UnlockExit(HLERequestContext& ctx) { | 350 | void ISelfController::UnlockExit(HLERequestContext& ctx) { |
| 350 | LOG_DEBUG(Service_AM, "called"); | 351 | LOG_DEBUG(Service_AM, "called"); |
| 351 | 352 | ||
| 352 | system.SetExitLock(false); | 353 | system.SetExitLocked(false); |
| 353 | 354 | ||
| 354 | IPC::ResponseBuilder rb{ctx, 2}; | 355 | IPC::ResponseBuilder rb{ctx, 2}; |
| 355 | rb.Push(ResultSuccess); | 356 | rb.Push(ResultSuccess); |
| 357 | |||
| 358 | if (system.GetExitRequested()) { | ||
| 359 | system.Exit(); | ||
| 360 | } | ||
| 356 | } | 361 | } |
| 357 | 362 | ||
| 358 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { | 363 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { |
| @@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) { | |||
| 833 | IPC::ResponseBuilder rb{ctx, 4}; | 838 | IPC::ResponseBuilder rb{ctx, 4}; |
| 834 | rb.Push(ResultSuccess); | 839 | rb.Push(ResultSuccess); |
| 835 | 840 | ||
| 836 | if (Settings::values.use_docked_mode.GetValue()) { | 841 | if (Settings::IsDockedMode()) { |
| 837 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); | 842 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); |
| 838 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); | 843 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); |
| 839 | } else { | 844 | } else { |
| @@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) { | |||
| 921 | } | 926 | } |
| 922 | 927 | ||
| 923 | void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { | 928 | void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { |
| 924 | const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()}; | 929 | const bool use_docked_mode{Settings::IsDockedMode()}; |
| 925 | LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); | 930 | LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); |
| 926 | 931 | ||
| 927 | IPC::ResponseBuilder rb{ctx, 3}; | 932 | IPC::ResponseBuilder rb{ctx, 3}; |
| @@ -1381,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) | |||
| 1381 | {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, | 1386 | {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, |
| 1382 | {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, | 1387 | {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, |
| 1383 | {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, | 1388 | {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, |
| 1384 | {28, nullptr, "GetSaveDataSizeMax"}, | 1389 | {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"}, |
| 1385 | {29, nullptr, "GetCacheStorageMax"}, | 1390 | {29, nullptr, "GetCacheStorageMax"}, |
| 1386 | {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, | 1391 | {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, |
| 1387 | {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, | 1392 | {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, |
| @@ -1513,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { | |||
| 1513 | IPC::RequestParser rp{ctx}; | 1518 | IPC::RequestParser rp{ctx}; |
| 1514 | const auto kind = rp.PopEnum<LaunchParameterKind>(); | 1519 | const auto kind = rp.PopEnum<LaunchParameterKind>(); |
| 1515 | 1520 | ||
| 1516 | LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); | 1521 | LOG_INFO(Service_AM, "called, kind={:08X}", kind); |
| 1517 | 1522 | ||
| 1518 | if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { | 1523 | if (kind == LaunchParameterKind::UserChannel) { |
| 1519 | const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { | 1524 | auto channel = system.GetUserChannel(); |
| 1520 | return system.GetFileSystemController().GetBCATDirectory(tid); | 1525 | if (channel.empty()) { |
| 1521 | }); | 1526 | LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); |
| 1522 | const auto build_id_full = system.GetApplicationProcessBuildID(); | 1527 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1523 | u64 build_id{}; | 1528 | rb.Push(AM::ResultNoDataInChannel); |
| 1524 | std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); | ||
| 1525 | |||
| 1526 | auto data = | ||
| 1527 | backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id}); | ||
| 1528 | if (data.has_value()) { | ||
| 1529 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 1530 | rb.Push(ResultSuccess); | ||
| 1531 | rb.PushIpcInterface<IStorage>(system, std::move(*data)); | ||
| 1532 | launch_popped_application_specific = true; | ||
| 1533 | return; | 1529 | return; |
| 1534 | } | 1530 | } |
| 1531 | |||
| 1532 | auto data = channel.back(); | ||
| 1533 | channel.pop_back(); | ||
| 1534 | |||
| 1535 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 1536 | rb.Push(ResultSuccess); | ||
| 1537 | rb.PushIpcInterface<IStorage>(system, std::move(data)); | ||
| 1535 | } else if (kind == LaunchParameterKind::AccountPreselectedUser && | 1538 | } else if (kind == LaunchParameterKind::AccountPreselectedUser && |
| 1536 | !launch_popped_account_preselect) { | 1539 | !launch_popped_account_preselect) { |
| 1540 | // TODO: Verify this is hw-accurate | ||
| 1537 | LaunchParameterAccountPreselectedUser params{}; | 1541 | LaunchParameterAccountPreselectedUser params{}; |
| 1538 | 1542 | ||
| 1539 | params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; | 1543 | params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; |
| @@ -1545,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { | |||
| 1545 | params.current_user = *uuid; | 1549 | params.current_user = *uuid; |
| 1546 | 1550 | ||
| 1547 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 1551 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 1548 | |||
| 1549 | rb.Push(ResultSuccess); | 1552 | rb.Push(ResultSuccess); |
| 1550 | 1553 | ||
| 1551 | std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); | 1554 | std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); |
| @@ -1553,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { | |||
| 1553 | 1556 | ||
| 1554 | rb.PushIpcInterface<IStorage>(system, std::move(buffer)); | 1557 | rb.PushIpcInterface<IStorage>(system, std::move(buffer)); |
| 1555 | launch_popped_account_preselect = true; | 1558 | launch_popped_account_preselect = true; |
| 1556 | return; | 1559 | } else { |
| 1560 | LOG_ERROR(Service_AM, "Unknown launch parameter kind."); | ||
| 1561 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1562 | rb.Push(AM::ResultNoDataInChannel); | ||
| 1557 | } | 1563 | } |
| 1558 | |||
| 1559 | LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); | ||
| 1560 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1561 | rb.Push(AM::ResultNoDataInChannel); | ||
| 1562 | } | 1564 | } |
| 1563 | 1565 | ||
| 1564 | void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { | 1566 | void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { |
| @@ -1819,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) { | |||
| 1819 | rb.PushRaw(resp); | 1821 | rb.PushRaw(resp); |
| 1820 | } | 1822 | } |
| 1821 | 1823 | ||
| 1824 | void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) { | ||
| 1825 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1826 | |||
| 1827 | constexpr u64 size_max_normal = 0xFFFFFFF; | ||
| 1828 | constexpr u64 size_max_journal = 0xFFFFFFF; | ||
| 1829 | |||
| 1830 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 1831 | rb.Push(ResultSuccess); | ||
| 1832 | rb.Push(size_max_normal); | ||
| 1833 | rb.Push(size_max_journal); | ||
| 1834 | } | ||
| 1835 | |||
| 1822 | void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { | 1836 | void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { |
| 1823 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1837 | LOG_WARNING(Service_AM, "(STUBBED) called"); |
| 1824 | 1838 | ||
| @@ -1850,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) { | |||
| 1850 | } | 1864 | } |
| 1851 | 1865 | ||
| 1852 | void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { | 1866 | void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { |
| 1853 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1867 | LOG_DEBUG(Service_AM, "called"); |
| 1868 | |||
| 1869 | system.GetUserChannel().clear(); | ||
| 1854 | 1870 | ||
| 1855 | IPC::ResponseBuilder rb{ctx, 2}; | 1871 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1856 | rb.Push(ResultSuccess); | 1872 | rb.Push(ResultSuccess); |
| 1857 | } | 1873 | } |
| 1858 | 1874 | ||
| 1859 | void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { | 1875 | void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { |
| 1860 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1876 | LOG_DEBUG(Service_AM, "called"); |
| 1877 | |||
| 1878 | IPC::RequestParser rp{ctx}; | ||
| 1879 | const auto storage = rp.PopIpcInterface<IStorage>().lock(); | ||
| 1880 | if (storage) { | ||
| 1881 | system.GetUserChannel().push_back(storage->GetData()); | ||
| 1882 | } | ||
| 1861 | 1883 | ||
| 1862 | IPC::ResponseBuilder rb{ctx, 2}; | 1884 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1863 | rb.Push(ResultSuccess); | 1885 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index d68998f04..f86841c60 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h | |||
| @@ -316,6 +316,7 @@ private: | |||
| 316 | void ExtendSaveData(HLERequestContext& ctx); | 316 | void ExtendSaveData(HLERequestContext& ctx); |
| 317 | void GetSaveDataSize(HLERequestContext& ctx); | 317 | void GetSaveDataSize(HLERequestContext& ctx); |
| 318 | void CreateCacheStorage(HLERequestContext& ctx); | 318 | void CreateCacheStorage(HLERequestContext& ctx); |
| 319 | void GetSaveDataSizeMax(HLERequestContext& ctx); | ||
| 319 | void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); | 320 | void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); |
| 320 | void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); | 321 | void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); |
| 321 | void BeginBlockingHomeButton(HLERequestContext& ctx); | 322 | void BeginBlockingHomeButton(HLERequestContext& ctx); |
| @@ -339,7 +340,6 @@ private: | |||
| 339 | 340 | ||
| 340 | KernelHelpers::ServiceContext service_context; | 341 | KernelHelpers::ServiceContext service_context; |
| 341 | 342 | ||
| 342 | bool launch_popped_application_specific = false; | ||
| 343 | bool launch_popped_account_preselect = false; | 343 | bool launch_popped_account_preselect = false; |
| 344 | s32 previous_program_index{-1}; | 344 | s32 previous_program_index{-1}; |
| 345 | Kernel::KEvent* gpu_error_detected_event; | 345 | Kernel::KEvent* gpu_error_detected_event; |
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp index d1f652c09..350a90818 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.cpp +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp | |||
| @@ -85,15 +85,18 @@ void MiiEdit::Execute() { | |||
| 85 | break; | 85 | break; |
| 86 | case MiiEditAppletMode::CreateMii: | 86 | case MiiEditAppletMode::CreateMii: |
| 87 | case MiiEditAppletMode::EditMii: { | 87 | case MiiEditAppletMode::EditMii: { |
| 88 | Service::Mii::MiiManager mii_manager; | 88 | Mii::CharInfo char_info{}; |
| 89 | Mii::StoreData store_data{}; | ||
| 90 | store_data.BuildBase(Mii::Gender::Male); | ||
| 91 | char_info.SetFromStoreData(store_data); | ||
| 89 | 92 | ||
| 90 | const MiiEditCharInfo char_info{ | 93 | const MiiEditCharInfo edit_char_info{ |
| 91 | .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii | 94 | .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii |
| 92 | ? applet_input_v4.char_info.mii_info | 95 | ? applet_input_v4.char_info.mii_info |
| 93 | : mii_manager.BuildDefault(0)}, | 96 | : char_info}, |
| 94 | }; | 97 | }; |
| 95 | 98 | ||
| 96 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); | 99 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); |
| 97 | break; | 100 | break; |
| 98 | } | 101 | } |
| 99 | default: | 102 | default: |
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h index 4705d019f..f3d764073 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit_types.h +++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h | |||
| @@ -7,7 +7,8 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/common_funcs.h" | 8 | #include "common/common_funcs.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "core/hle/service/mii/types.h" | 10 | #include "common/uuid.h" |
| 11 | #include "core/hle/service/mii/types/char_info.h" | ||
| 11 | 12 | ||
| 12 | namespace Service::AM::Applets { | 13 | namespace Service::AM::Applets { |
| 13 | 14 | ||
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 2accf7898..1c9a1dc29 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp | |||
| @@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, | |||
| 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), | 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), |
| 140 | system.GetContentProvider()}; | 140 | system.GetContentProvider()}; |
| 141 | 141 | ||
| 142 | return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); | 142 | return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type); |
| 143 | } | 143 | } |
| 144 | } | 144 | } |
| 145 | 145 | ||
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp index 227fdd0cf..4f1aa5cc2 100644 --- a/src/core/hle/service/apm/apm_controller.cpp +++ b/src/core/hle/service/apm/apm_controller.cpp | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 10 | #include "common/settings_enums.h" | ||
| 10 | #include "core/core_timing.h" | 11 | #include "core/core_timing.h" |
| 11 | #include "core/hle/service/apm/apm_controller.h" | 12 | #include "core/hle/service/apm/apm_controller.h" |
| 12 | 13 | ||
| @@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { | |||
| 67 | } | 68 | } |
| 68 | 69 | ||
| 69 | PerformanceMode Controller::GetCurrentPerformanceMode() const { | 70 | PerformanceMode Controller::GetCurrentPerformanceMode() const { |
| 70 | return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost | 71 | return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal; |
| 71 | : PerformanceMode::Normal; | ||
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { | 74 | PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { |
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 526a39130..56fee4591 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp | |||
| @@ -220,7 +220,7 @@ AudInU::AudInU(Core::System& system_) | |||
| 220 | AudInU::~AudInU() = default; | 220 | AudInU::~AudInU() = default; |
| 221 | 221 | ||
| 222 | void AudInU::ListAudioIns(HLERequestContext& ctx) { | 222 | void AudInU::ListAudioIns(HLERequestContext& ctx) { |
| 223 | using namespace AudioCore::AudioRenderer; | 223 | using namespace AudioCore::Renderer; |
| 224 | 224 | ||
| 225 | LOG_DEBUG(Service_Audio, "called"); | 225 | LOG_DEBUG(Service_Audio, "called"); |
| 226 | 226 | ||
| @@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) { | |||
| 240 | } | 240 | } |
| 241 | 241 | ||
| 242 | void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { | 242 | void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { |
| 243 | using namespace AudioCore::AudioRenderer; | 243 | using namespace AudioCore::Renderer; |
| 244 | 244 | ||
| 245 | LOG_DEBUG(Service_Audio, "called"); | 245 | LOG_DEBUG(Service_Audio, "called"); |
| 246 | 246 | ||
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 23f84a29f..ca683d72c 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp | |||
| @@ -228,7 +228,7 @@ AudOutU::AudOutU(Core::System& system_) | |||
| 228 | AudOutU::~AudOutU() = default; | 228 | AudOutU::~AudOutU() = default; |
| 229 | 229 | ||
| 230 | void AudOutU::ListAudioOuts(HLERequestContext& ctx) { | 230 | void AudOutU::ListAudioOuts(HLERequestContext& ctx) { |
| 231 | using namespace AudioCore::AudioRenderer; | 231 | using namespace AudioCore::Renderer; |
| 232 | 232 | ||
| 233 | std::scoped_lock l{impl->mutex}; | 233 | std::scoped_lock l{impl->mutex}; |
| 234 | 234 | ||
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index b723b65c8..2f09cade5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -26,7 +26,7 @@ | |||
| 26 | #include "core/hle/service/ipc_helpers.h" | 26 | #include "core/hle/service/ipc_helpers.h" |
| 27 | #include "core/memory.h" | 27 | #include "core/memory.h" |
| 28 | 28 | ||
| 29 | using namespace AudioCore::AudioRenderer; | 29 | using namespace AudioCore::Renderer; |
| 30 | 30 | ||
| 31 | namespace Service::Audio { | 31 | namespace Service::Audio { |
| 32 | 32 | ||
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index d8e9c8719..3d7993a16 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h | |||
| @@ -28,7 +28,7 @@ private: | |||
| 28 | void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); | 28 | void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); |
| 29 | 29 | ||
| 30 | KernelHelpers::ServiceContext service_context; | 30 | KernelHelpers::ServiceContext service_context; |
| 31 | std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; | 31 | std::unique_ptr<AudioCore::Renderer::Manager> impl; |
| 32 | u32 num_audio_devices{0}; | 32 | u32 num_audio_devices{0}; |
| 33 | }; | 33 | }; |
| 34 | 34 | ||
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h | |||
| @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; | |||
| 20 | constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; | 20 | constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; |
| 21 | constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; | 21 | constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; |
| 22 | 22 | ||
| 23 | constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; | ||
| 24 | constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; | ||
| 25 | constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; | ||
| 26 | constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; | ||
| 27 | constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; | ||
| 28 | constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; | ||
| 29 | constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; | ||
| 30 | constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; | ||
| 31 | constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; | ||
| 32 | constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; | ||
| 33 | constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; | ||
| 34 | |||
| 23 | } // namespace Service::Audio | 35 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index fa77007f3..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -1,371 +1,506 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <chrono> | ||
| 5 | #include <cstring> | ||
| 6 | #include <memory> | 4 | #include <memory> |
| 7 | #include <vector> | 5 | #include <vector> |
| 8 | 6 | ||
| 9 | #include <opus.h> | 7 | #include "audio_core/opus/decoder.h" |
| 10 | #include <opus_multistream.h> | 8 | #include "audio_core/opus/parameters.h" |
| 11 | |||
| 12 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 13 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 14 | #include "common/scratch_buffer.h" | 11 | #include "common/scratch_buffer.h" |
| 12 | #include "core/core.h" | ||
| 15 | #include "core/hle/service/audio/hwopus.h" | 13 | #include "core/hle/service/audio/hwopus.h" |
| 16 | #include "core/hle/service/ipc_helpers.h" | 14 | #include "core/hle/service/ipc_helpers.h" |
| 17 | 15 | ||
| 18 | namespace Service::Audio { | 16 | namespace Service::Audio { |
| 19 | namespace { | 17 | using namespace AudioCore::OpusDecoder; |
| 20 | struct OpusDeleter { | 18 | |
| 21 | void operator()(OpusMSDecoder* ptr) const { | 19 | class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> { |
| 22 | opus_multistream_decoder_destroy(ptr); | 20 | public: |
| 21 | explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) | ||
| 22 | : ServiceFramework{system_, "IHardwareOpusDecoder"}, | ||
| 23 | impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} { | ||
| 24 | // clang-format off | ||
| 25 | static const FunctionInfo functions[] = { | ||
| 26 | {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 27 | {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, | ||
| 28 | {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, | ||
| 29 | {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, | ||
| 30 | {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 31 | {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 32 | {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, | ||
| 33 | {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, | ||
| 34 | {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 35 | {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, | ||
| 36 | }; | ||
| 37 | // clang-format on | ||
| 38 | |||
| 39 | RegisterHandlers(functions); | ||
| 23 | } | 40 | } |
| 24 | }; | ||
| 25 | 41 | ||
| 26 | using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; | 42 | Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, |
| 43 | u64 transfer_memory_size) { | ||
| 44 | return impl->Initialize(params, transfer_memory, transfer_memory_size); | ||
| 45 | } | ||
| 27 | 46 | ||
| 28 | struct OpusPacketHeader { | 47 | Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, |
| 29 | // Packet size in bytes. | 48 | u64 transfer_memory_size) { |
| 30 | u32_be size; | 49 | return impl->Initialize(params, transfer_memory, transfer_memory_size); |
| 31 | // Indicates the final range of the codec's entropy coder. | 50 | } |
| 32 | u32_be final_range; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); | ||
| 35 | 51 | ||
| 36 | class OpusDecoderState { | 52 | private: |
| 37 | public: | 53 | void DecodeInterleavedOld(HLERequestContext& ctx) { |
| 38 | /// Describes extra behavior that may be asked of the decoding context. | 54 | IPC::RequestParser rp{ctx}; |
| 39 | enum class ExtraBehavior { | ||
| 40 | /// No extra behavior. | ||
| 41 | None, | ||
| 42 | 55 | ||
| 43 | /// Resets the decoder context back to a freshly initialized state. | 56 | auto input_data{ctx.ReadBuffer(0)}; |
| 44 | ResetContext, | 57 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 45 | }; | ||
| 46 | 58 | ||
| 47 | enum class PerfTime { | 59 | u32 size{}; |
| 48 | Disabled, | 60 | u32 sample_count{}; |
| 49 | Enabled, | 61 | auto result = |
| 50 | }; | 62 | impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); |
| 63 | |||
| 64 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); | ||
| 65 | |||
| 66 | ctx.WriteBuffer(output_data); | ||
| 51 | 67 | ||
| 52 | explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) | 68 | IPC::ResponseBuilder rb{ctx, 4}; |
| 53 | : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} | 69 | rb.Push(result); |
| 54 | 70 | rb.Push(size); | |
| 55 | // Decodes interleaved Opus packets. Optionally allows reporting time taken to | 71 | rb.Push(sample_count); |
| 56 | // perform the decoding, as well as any relevant extra behavior. | ||
| 57 | void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, | ||
| 58 | ExtraBehavior extra_behavior) { | ||
| 59 | if (perf_time == PerfTime::Disabled) { | ||
| 60 | DecodeInterleavedHelper(ctx, nullptr, extra_behavior); | ||
| 61 | } else { | ||
| 62 | u64 performance = 0; | ||
| 63 | DecodeInterleavedHelper(ctx, &performance, extra_behavior); | ||
| 64 | } | ||
| 65 | } | 72 | } |
| 66 | 73 | ||
| 67 | private: | 74 | void SetContext(HLERequestContext& ctx) { |
| 68 | void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, | 75 | IPC::RequestParser rp{ctx}; |
| 69 | ExtraBehavior extra_behavior) { | 76 | |
| 70 | u32 consumed = 0; | 77 | LOG_DEBUG(Service_Audio, "called"); |
| 71 | u32 sample_count = 0; | 78 | |
| 72 | samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); | 79 | auto input_data{ctx.ReadBuffer(0)}; |
| 73 | 80 | auto result = impl->SetContext(input_data); | |
| 74 | if (extra_behavior == ExtraBehavior::ResetContext) { | 81 | |
| 75 | ResetDecoderContext(); | 82 | IPC::ResponseBuilder rb{ctx, 2}; |
| 76 | } | 83 | rb.Push(result); |
| 77 | |||
| 78 | if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { | ||
| 79 | LOG_ERROR(Audio, "Failed to decode opus data"); | ||
| 80 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 81 | // TODO(ogniK): Use correct error code | ||
| 82 | rb.Push(ResultUnknown); | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | |||
| 86 | const u32 param_size = performance != nullptr ? 6 : 4; | ||
| 87 | IPC::ResponseBuilder rb{ctx, param_size}; | ||
| 88 | rb.Push(ResultSuccess); | ||
| 89 | rb.Push<u32>(consumed); | ||
| 90 | rb.Push<u32>(sample_count); | ||
| 91 | if (performance) { | ||
| 92 | rb.Push<u64>(*performance); | ||
| 93 | } | ||
| 94 | ctx.WriteBuffer(samples); | ||
| 95 | } | 84 | } |
| 96 | 85 | ||
| 97 | bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, | 86 | void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { |
| 98 | std::span<opus_int16> output, u64* out_performance_time) const { | 87 | IPC::RequestParser rp{ctx}; |
| 99 | const auto start_time = std::chrono::steady_clock::now(); | 88 | |
| 100 | const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); | 89 | auto input_data{ctx.ReadBuffer(0)}; |
| 101 | if (sizeof(OpusPacketHeader) > input.size()) { | 90 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 102 | LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", | 91 | |
| 103 | sizeof(OpusPacketHeader), input.size()); | 92 | u32 size{}; |
| 104 | return false; | 93 | u32 sample_count{}; |
| 105 | } | 94 | auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, |
| 106 | 95 | input_data, output_data, false); | |
| 107 | OpusPacketHeader hdr{}; | 96 | |
| 108 | std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); | 97 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); |
| 109 | if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { | 98 | |
| 110 | LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", | 99 | ctx.WriteBuffer(output_data); |
| 111 | sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); | 100 | |
| 112 | return false; | 101 | IPC::ResponseBuilder rb{ctx, 4}; |
| 113 | } | 102 | rb.Push(result); |
| 114 | 103 | rb.Push(size); | |
| 115 | const auto frame = input.data() + sizeof(OpusPacketHeader); | 104 | rb.Push(sample_count); |
| 116 | const auto decoded_sample_count = opus_packet_get_nb_samples( | ||
| 117 | frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), | ||
| 118 | static_cast<opus_int32>(sample_rate)); | ||
| 119 | if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { | ||
| 120 | LOG_ERROR( | ||
| 121 | Audio, | ||
| 122 | "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", | ||
| 123 | decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); | ||
| 128 | const auto out_sample_count = | ||
| 129 | opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); | ||
| 130 | if (out_sample_count < 0) { | ||
| 131 | LOG_ERROR(Audio, | ||
| 132 | "Incorrect sample count received from opus_decode, " | ||
| 133 | "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", | ||
| 134 | out_sample_count, frame_size, static_cast<u32>(hdr.size)); | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | |||
| 138 | const auto end_time = std::chrono::steady_clock::now() - start_time; | ||
| 139 | sample_count = out_sample_count; | ||
| 140 | consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); | ||
| 141 | if (out_performance_time != nullptr) { | ||
| 142 | *out_performance_time = | ||
| 143 | std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); | ||
| 144 | } | ||
| 145 | |||
| 146 | return true; | ||
| 147 | } | 105 | } |
| 148 | 106 | ||
| 149 | void ResetDecoderContext() { | 107 | void SetContextForMultiStream(HLERequestContext& ctx) { |
| 150 | ASSERT(decoder != nullptr); | 108 | IPC::RequestParser rp{ctx}; |
| 109 | |||
| 110 | LOG_DEBUG(Service_Audio, "called"); | ||
| 111 | |||
| 112 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 113 | auto result = impl->SetContext(input_data); | ||
| 151 | 114 | ||
| 152 | opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); | 115 | IPC::ResponseBuilder rb{ctx, 2}; |
| 116 | rb.Push(result); | ||
| 153 | } | 117 | } |
| 154 | 118 | ||
| 155 | OpusDecoderPtr decoder; | 119 | void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { |
| 156 | u32 sample_rate; | 120 | IPC::RequestParser rp{ctx}; |
| 157 | u32 channel_count; | ||
| 158 | Common::ScratchBuffer<opus_int16> samples; | ||
| 159 | }; | ||
| 160 | 121 | ||
| 161 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { | 122 | auto input_data{ctx.ReadBuffer(0)}; |
| 162 | public: | 123 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 163 | explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) | ||
| 164 | : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ | ||
| 165 | std::move(decoder_state_)} { | ||
| 166 | // clang-format off | ||
| 167 | static const FunctionInfo functions[] = { | ||
| 168 | {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 169 | {1, nullptr, "SetContext"}, | ||
| 170 | {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, | ||
| 171 | {3, nullptr, "SetContextForMultiStream"}, | ||
| 172 | {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 173 | {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 174 | {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, | ||
| 175 | {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, | ||
| 176 | {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 177 | {9, nullptr, "DecodeInterleavedForMultiStream"}, | ||
| 178 | }; | ||
| 179 | // clang-format on | ||
| 180 | 124 | ||
| 181 | RegisterHandlers(functions); | 125 | u32 size{}; |
| 126 | u32 sample_count{}; | ||
| 127 | u64 time_taken{}; | ||
| 128 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 129 | output_data, false); | ||
| 130 | |||
| 131 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, | ||
| 132 | sample_count, time_taken); | ||
| 133 | |||
| 134 | ctx.WriteBuffer(output_data); | ||
| 135 | |||
| 136 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 137 | rb.Push(result); | ||
| 138 | rb.Push(size); | ||
| 139 | rb.Push(sample_count); | ||
| 140 | rb.Push(time_taken); | ||
| 182 | } | 141 | } |
| 183 | 142 | ||
| 184 | private: | 143 | void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { |
| 185 | void DecodeInterleavedOld(HLERequestContext& ctx) { | 144 | IPC::RequestParser rp{ctx}; |
| 186 | LOG_DEBUG(Audio, "called"); | 145 | |
| 146 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 147 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 148 | |||
| 149 | u32 size{}; | ||
| 150 | u32 sample_count{}; | ||
| 151 | u64 time_taken{}; | ||
| 152 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 153 | input_data, output_data, false); | ||
| 187 | 154 | ||
| 188 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, | 155 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, |
| 189 | OpusDecoderState::ExtraBehavior::None); | 156 | sample_count, time_taken); |
| 157 | |||
| 158 | ctx.WriteBuffer(output_data); | ||
| 159 | |||
| 160 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 161 | rb.Push(result); | ||
| 162 | rb.Push(size); | ||
| 163 | rb.Push(sample_count); | ||
| 164 | rb.Push(time_taken); | ||
| 190 | } | 165 | } |
| 191 | 166 | ||
| 192 | void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { | 167 | void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { |
| 193 | LOG_DEBUG(Audio, "called"); | 168 | IPC::RequestParser rp{ctx}; |
| 169 | |||
| 170 | auto reset{rp.Pop<bool>()}; | ||
| 171 | |||
| 172 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 173 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 174 | |||
| 175 | u32 size{}; | ||
| 176 | u32 sample_count{}; | ||
| 177 | u64 time_taken{}; | ||
| 178 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 179 | output_data, reset); | ||
| 180 | |||
| 181 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 182 | reset, size, sample_count, time_taken); | ||
| 183 | |||
| 184 | ctx.WriteBuffer(output_data); | ||
| 185 | |||
| 186 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 187 | rb.Push(result); | ||
| 188 | rb.Push(size); | ||
| 189 | rb.Push(sample_count); | ||
| 190 | rb.Push(time_taken); | ||
| 191 | } | ||
| 192 | |||
| 193 | void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { | ||
| 194 | IPC::RequestParser rp{ctx}; | ||
| 195 | |||
| 196 | auto reset{rp.Pop<bool>()}; | ||
| 197 | |||
| 198 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 199 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 200 | |||
| 201 | u32 size{}; | ||
| 202 | u32 sample_count{}; | ||
| 203 | u64 time_taken{}; | ||
| 204 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 205 | input_data, output_data, reset); | ||
| 206 | |||
| 207 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 208 | reset, size, sample_count, time_taken); | ||
| 194 | 209 | ||
| 195 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, | 210 | ctx.WriteBuffer(output_data); |
| 196 | OpusDecoderState::ExtraBehavior::None); | 211 | |
| 212 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 213 | rb.Push(result); | ||
| 214 | rb.Push(size); | ||
| 215 | rb.Push(sample_count); | ||
| 216 | rb.Push(time_taken); | ||
| 197 | } | 217 | } |
| 198 | 218 | ||
| 199 | void DecodeInterleaved(HLERequestContext& ctx) { | 219 | void DecodeInterleaved(HLERequestContext& ctx) { |
| 200 | LOG_DEBUG(Audio, "called"); | 220 | IPC::RequestParser rp{ctx}; |
| 221 | |||
| 222 | auto reset{rp.Pop<bool>()}; | ||
| 223 | |||
| 224 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 225 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 226 | |||
| 227 | u32 size{}; | ||
| 228 | u32 sample_count{}; | ||
| 229 | u64 time_taken{}; | ||
| 230 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 231 | output_data, reset); | ||
| 232 | |||
| 233 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 234 | reset, size, sample_count, time_taken); | ||
| 235 | |||
| 236 | ctx.WriteBuffer(output_data); | ||
| 237 | |||
| 238 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 239 | rb.Push(result); | ||
| 240 | rb.Push(size); | ||
| 241 | rb.Push(sample_count); | ||
| 242 | rb.Push(time_taken); | ||
| 243 | } | ||
| 201 | 244 | ||
| 245 | void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { | ||
| 202 | IPC::RequestParser rp{ctx}; | 246 | IPC::RequestParser rp{ctx}; |
| 203 | const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext | ||
| 204 | : OpusDecoderState::ExtraBehavior::None; | ||
| 205 | 247 | ||
| 206 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); | 248 | auto reset{rp.Pop<bool>()}; |
| 249 | |||
| 250 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 251 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 252 | |||
| 253 | u32 size{}; | ||
| 254 | u32 sample_count{}; | ||
| 255 | u64 time_taken{}; | ||
| 256 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 257 | input_data, output_data, reset); | ||
| 258 | |||
| 259 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 260 | reset, size, sample_count, time_taken); | ||
| 261 | |||
| 262 | ctx.WriteBuffer(output_data); | ||
| 263 | |||
| 264 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 265 | rb.Push(result); | ||
| 266 | rb.Push(size); | ||
| 267 | rb.Push(sample_count); | ||
| 268 | rb.Push(time_taken); | ||
| 207 | } | 269 | } |
| 208 | 270 | ||
| 209 | OpusDecoderState decoder_state; | 271 | std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl; |
| 272 | Common::ScratchBuffer<u8> output_data; | ||
| 210 | }; | 273 | }; |
| 211 | 274 | ||
| 212 | std::size_t WorkerBufferSize(u32 channel_count) { | 275 | void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { |
| 213 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 276 | IPC::RequestParser rp{ctx}; |
| 214 | constexpr int num_streams = 1; | ||
| 215 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 216 | return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); | ||
| 217 | } | ||
| 218 | 277 | ||
| 219 | // Creates the mapping table that maps the input channels to the particular | 278 | auto params = rp.PopRaw<OpusParameters>(); |
| 220 | // output channels. In the stereo case, we map the left and right input channels | 279 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 221 | // to the left and right output channels respectively. | 280 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 222 | // | 281 | auto transfer_memory{ |
| 223 | // However, in the monophonic case, we only map the one available channel | 282 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( |
| 224 | // to the sole output channel. We specify 255 for the would-be right channel | 283 | transfer_memory_handle)}; |
| 225 | // as this is a special value defined by Opus to indicate to the decoder to | ||
| 226 | // ignore that channel. | ||
| 227 | std::array<u8, 2> CreateMappingTable(u32 channel_count) { | ||
| 228 | if (channel_count == 2) { | ||
| 229 | return {{0, 1}}; | ||
| 230 | } | ||
| 231 | 284 | ||
| 232 | return {{0, 255}}; | 285 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", |
| 286 | params.sample_rate, params.channel_count, transfer_memory_size); | ||
| 287 | |||
| 288 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; | ||
| 289 | |||
| 290 | OpusParametersEx ex{ | ||
| 291 | .sample_rate = params.sample_rate, | ||
| 292 | .channel_count = params.channel_count, | ||
| 293 | .use_large_frame_size = false, | ||
| 294 | }; | ||
| 295 | auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); | ||
| 296 | |||
| 297 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 298 | rb.Push(result); | ||
| 299 | rb.PushIpcInterface(decoder); | ||
| 233 | } | 300 | } |
| 234 | } // Anonymous namespace | ||
| 235 | 301 | ||
| 236 | void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { | 302 | void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { |
| 237 | IPC::RequestParser rp{ctx}; | 303 | IPC::RequestParser rp{ctx}; |
| 238 | const auto sample_rate = rp.Pop<u32>(); | 304 | auto params = rp.PopRaw<OpusParameters>(); |
| 239 | const auto channel_count = rp.Pop<u32>(); | ||
| 240 | 305 | ||
| 241 | LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); | 306 | u64 size{}; |
| 307 | auto result = impl.GetWorkBufferSize(params, size); | ||
| 242 | 308 | ||
| 243 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 309 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", |
| 244 | sample_rate == 12000 || sample_rate == 8000, | 310 | params.sample_rate, params.channel_count, size); |
| 245 | "Invalid sample rate"); | 311 | |
| 246 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 312 | IPC::ResponseBuilder rb{ctx, 4}; |
| 313 | rb.Push(result); | ||
| 314 | rb.Push(size); | ||
| 315 | } | ||
| 316 | |||
| 317 | void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { | ||
| 318 | IPC::RequestParser rp{ctx}; | ||
| 247 | 319 | ||
| 248 | const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); | 320 | auto input{ctx.ReadBuffer()}; |
| 249 | LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); | 321 | OpusMultiStreamParameters params; |
| 322 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); | ||
| 323 | |||
| 324 | auto transfer_memory_size{rp.Pop<u32>()}; | ||
| 325 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; | ||
| 326 | auto transfer_memory{ | ||
| 327 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 328 | transfer_memory_handle)}; | ||
| 329 | |||
| 330 | LOG_DEBUG(Service_Audio, | ||
| 331 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " | ||
| 332 | "transfer_memory_size 0x{:X}", | ||
| 333 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 334 | params.stereo_stream_count, transfer_memory_size); | ||
| 335 | |||
| 336 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; | ||
| 337 | |||
| 338 | OpusMultiStreamParametersEx ex{ | ||
| 339 | .sample_rate = params.sample_rate, | ||
| 340 | .channel_count = params.channel_count, | ||
| 341 | .total_stream_count = params.total_stream_count, | ||
| 342 | .stereo_stream_count = params.stereo_stream_count, | ||
| 343 | .use_large_frame_size = false, | ||
| 344 | .mappings{}, | ||
| 345 | }; | ||
| 346 | std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); | ||
| 347 | auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); | ||
| 250 | 348 | ||
| 251 | IPC::ResponseBuilder rb{ctx, 3}; | 349 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 252 | rb.Push(ResultSuccess); | 350 | rb.Push(result); |
| 253 | rb.Push<u32>(worker_buffer_sz); | 351 | rb.PushIpcInterface(decoder); |
| 254 | } | 352 | } |
| 255 | 353 | ||
| 256 | void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { | 354 | void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { |
| 257 | GetWorkBufferSize(ctx); | 355 | IPC::RequestParser rp{ctx}; |
| 356 | |||
| 357 | auto input{ctx.ReadBuffer()}; | ||
| 358 | OpusMultiStreamParameters params; | ||
| 359 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); | ||
| 360 | |||
| 361 | u64 size{}; | ||
| 362 | auto result = impl.GetWorkBufferSizeForMultiStream(params, size); | ||
| 363 | |||
| 364 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 365 | |||
| 366 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 367 | rb.Push(result); | ||
| 368 | rb.Push(size); | ||
| 258 | } | 369 | } |
| 259 | 370 | ||
| 260 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { | 371 | void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { |
| 261 | OpusMultiStreamParametersEx param; | 372 | IPC::RequestParser rp{ctx}; |
| 262 | std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); | ||
| 263 | 373 | ||
| 264 | const auto sample_rate = param.sample_rate; | 374 | auto params = rp.PopRaw<OpusParametersEx>(); |
| 265 | const auto channel_count = param.channel_count; | 375 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 266 | const auto number_streams = param.number_streams; | 376 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 267 | const auto number_stereo_streams = param.number_stereo_streams; | 377 | auto transfer_memory{ |
| 378 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 379 | transfer_memory_handle)}; | ||
| 268 | 380 | ||
| 269 | LOG_DEBUG( | 381 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", |
| 270 | Audio, | 382 | params.sample_rate, params.channel_count, transfer_memory_size); |
| 271 | "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", | ||
| 272 | sample_rate, channel_count, number_streams, number_stereo_streams); | ||
| 273 | 383 | ||
| 274 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 384 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 275 | sample_rate == 12000 || sample_rate == 8000, | ||
| 276 | "Invalid sample rate"); | ||
| 277 | 385 | ||
| 278 | const u32 worker_buffer_sz = | 386 | auto result = |
| 279 | static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); | 387 | decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); |
| 280 | 388 | ||
| 281 | IPC::ResponseBuilder rb{ctx, 3}; | 389 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 282 | rb.Push(ResultSuccess); | 390 | rb.Push(result); |
| 283 | rb.Push<u32>(worker_buffer_sz); | 391 | rb.PushIpcInterface(decoder); |
| 284 | } | 392 | } |
| 285 | 393 | ||
| 286 | void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { | 394 | void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { |
| 287 | IPC::RequestParser rp{ctx}; | 395 | IPC::RequestParser rp{ctx}; |
| 288 | const auto sample_rate = rp.Pop<u32>(); | 396 | auto params = rp.PopRaw<OpusParametersEx>(); |
| 289 | const auto channel_count = rp.Pop<u32>(); | ||
| 290 | const auto buffer_sz = rp.Pop<u32>(); | ||
| 291 | |||
| 292 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, | ||
| 293 | channel_count, buffer_sz); | ||
| 294 | |||
| 295 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | ||
| 296 | sample_rate == 12000 || sample_rate == 8000, | ||
| 297 | "Invalid sample rate"); | ||
| 298 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 299 | |||
| 300 | const std::size_t worker_sz = WorkerBufferSize(channel_count); | ||
| 301 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); | ||
| 302 | |||
| 303 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 304 | const auto mapping_table = CreateMappingTable(channel_count); | ||
| 305 | |||
| 306 | int error = 0; | ||
| 307 | OpusDecoderPtr decoder{ | ||
| 308 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | ||
| 309 | num_stereo_streams, mapping_table.data(), &error)}; | ||
| 310 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 311 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 312 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 313 | // TODO(ogniK): Use correct error code | ||
| 314 | rb.Push(ResultUnknown); | ||
| 315 | return; | ||
| 316 | } | ||
| 317 | 397 | ||
| 318 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 398 | u64 size{}; |
| 319 | rb.Push(ResultSuccess); | 399 | auto result = impl.GetWorkBufferSizeEx(params, size); |
| 320 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 400 | |
| 321 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | 401 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); |
| 402 | |||
| 403 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 404 | rb.Push(result); | ||
| 405 | rb.Push(size); | ||
| 322 | } | 406 | } |
| 323 | 407 | ||
| 324 | void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { | 408 | void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { |
| 325 | IPC::RequestParser rp{ctx}; | 409 | IPC::RequestParser rp{ctx}; |
| 326 | const auto sample_rate = rp.Pop<u32>(); | ||
| 327 | const auto channel_count = rp.Pop<u32>(); | ||
| 328 | 410 | ||
| 329 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); | 411 | auto input{ctx.ReadBuffer()}; |
| 412 | OpusMultiStreamParametersEx params; | ||
| 413 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 330 | 414 | ||
| 331 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 415 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 332 | sample_rate == 12000 || sample_rate == 8000, | 416 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 333 | "Invalid sample rate"); | 417 | auto transfer_memory{ |
| 334 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 418 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( |
| 419 | transfer_memory_handle)}; | ||
| 335 | 420 | ||
| 336 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | 421 | LOG_DEBUG(Service_Audio, |
| 337 | const auto mapping_table = CreateMappingTable(channel_count); | 422 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " |
| 423 | "use_large_frame_size {}" | ||
| 424 | "transfer_memory_size 0x{:X}", | ||
| 425 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 426 | params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); | ||
| 338 | 427 | ||
| 339 | int error = 0; | 428 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 340 | OpusDecoderPtr decoder{ | 429 | |
| 341 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | 430 | auto result = |
| 342 | num_stereo_streams, mapping_table.data(), &error)}; | 431 | decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); |
| 343 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 344 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 345 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 346 | // TODO(ogniK): Use correct error code | ||
| 347 | rb.Push(ResultUnknown); | ||
| 348 | return; | ||
| 349 | } | ||
| 350 | 432 | ||
| 351 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 433 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 352 | rb.Push(ResultSuccess); | 434 | rb.Push(result); |
| 353 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 435 | rb.PushIpcInterface(decoder); |
| 354 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | 436 | } |
| 437 | |||
| 438 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { | ||
| 439 | IPC::RequestParser rp{ctx}; | ||
| 440 | |||
| 441 | auto input{ctx.ReadBuffer()}; | ||
| 442 | OpusMultiStreamParametersEx params; | ||
| 443 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 444 | |||
| 445 | u64 size{}; | ||
| 446 | auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); | ||
| 447 | |||
| 448 | LOG_DEBUG(Service_Audio, | ||
| 449 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " | ||
| 450 | "use_large_frame_size {} -- returned size 0x{:X}", | ||
| 451 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 452 | params.stereo_stream_count, params.use_large_frame_size, size); | ||
| 453 | |||
| 454 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 455 | rb.Push(result); | ||
| 456 | rb.Push(size); | ||
| 457 | } | ||
| 458 | |||
| 459 | void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { | ||
| 460 | IPC::RequestParser rp{ctx}; | ||
| 461 | auto params = rp.PopRaw<OpusParametersEx>(); | ||
| 462 | |||
| 463 | u64 size{}; | ||
| 464 | auto result = impl.GetWorkBufferSizeExEx(params, size); | ||
| 465 | |||
| 466 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 467 | |||
| 468 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 469 | rb.Push(result); | ||
| 470 | rb.Push(size); | ||
| 471 | } | ||
| 472 | |||
| 473 | void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { | ||
| 474 | IPC::RequestParser rp{ctx}; | ||
| 475 | |||
| 476 | auto input{ctx.ReadBuffer()}; | ||
| 477 | OpusMultiStreamParametersEx params; | ||
| 478 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 479 | |||
| 480 | u64 size{}; | ||
| 481 | auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); | ||
| 482 | |||
| 483 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 484 | |||
| 485 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 486 | rb.Push(result); | ||
| 487 | rb.Push(size); | ||
| 355 | } | 488 | } |
| 356 | 489 | ||
| 357 | HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { | 490 | HwOpus::HwOpus(Core::System& system_) |
| 491 | : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { | ||
| 358 | static const FunctionInfo functions[] = { | 492 | static const FunctionInfo functions[] = { |
| 359 | {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, | 493 | {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, |
| 360 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, | 494 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, |
| 361 | {2, nullptr, "OpenOpusDecoderForMultiStream"}, | 495 | {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, |
| 362 | {3, nullptr, "GetWorkBufferSizeForMultiStream"}, | 496 | {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, |
| 363 | {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, | 497 | {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, |
| 364 | {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, | 498 | {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, |
| 365 | {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, | 499 | {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, |
| 500 | "OpenHardwareOpusDecoderForMultiStreamEx"}, | ||
| 366 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, | 501 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, |
| 367 | {8, nullptr, "GetWorkBufferSizeExEx"}, | 502 | {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, |
| 368 | {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, | 503 | {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, |
| 369 | }; | 504 | }; |
| 370 | RegisterHandlers(functions); | 505 | RegisterHandlers(functions); |
| 371 | } | 506 | } |
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index ece65c02c..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "audio_core/opus/decoder_manager.h" | ||
| 6 | #include "core/hle/service/service.h" | 7 | #include "core/hle/service/service.h" |
| 7 | 8 | ||
| 8 | namespace Core { | 9 | namespace Core { |
| @@ -11,16 +12,6 @@ class System; | |||
| 11 | 12 | ||
| 12 | namespace Service::Audio { | 13 | namespace Service::Audio { |
| 13 | 14 | ||
| 14 | struct OpusMultiStreamParametersEx { | ||
| 15 | u32 sample_rate; | ||
| 16 | u32 channel_count; | ||
| 17 | u32 number_streams; | ||
| 18 | u32 number_stereo_streams; | ||
| 19 | u32 use_large_frame_size; | ||
| 20 | u32 padding; | ||
| 21 | std::array<u32, 64> channel_mappings; | ||
| 22 | }; | ||
| 23 | |||
| 24 | class HwOpus final : public ServiceFramework<HwOpus> { | 15 | class HwOpus final : public ServiceFramework<HwOpus> { |
| 25 | public: | 16 | public: |
| 26 | explicit HwOpus(Core::System& system_); | 17 | explicit HwOpus(Core::System& system_); |
| @@ -28,10 +19,18 @@ public: | |||
| 28 | 19 | ||
| 29 | private: | 20 | private: |
| 30 | void OpenHardwareOpusDecoder(HLERequestContext& ctx); | 21 | void OpenHardwareOpusDecoder(HLERequestContext& ctx); |
| 31 | void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); | ||
| 32 | void GetWorkBufferSize(HLERequestContext& ctx); | 22 | void GetWorkBufferSize(HLERequestContext& ctx); |
| 23 | void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); | ||
| 24 | void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); | ||
| 25 | void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); | ||
| 33 | void GetWorkBufferSizeEx(HLERequestContext& ctx); | 26 | void GetWorkBufferSizeEx(HLERequestContext& ctx); |
| 27 | void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); | ||
| 34 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); | 28 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); |
| 29 | void GetWorkBufferSizeExEx(HLERequestContext& ctx); | ||
| 30 | void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); | ||
| 31 | |||
| 32 | Core::System& system; | ||
| 33 | AudioCore::OpusDecoder::OpusDecoderManager impl; | ||
| 35 | }; | 34 | }; |
| 36 | 35 | ||
| 37 | } // namespace Service::Audio | 36 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 446f46b3c..9eaae4c4b 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp | |||
| @@ -122,20 +122,18 @@ private: | |||
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | void ImportTicket(HLERequestContext& ctx) { | 124 | void ImportTicket(HLERequestContext& ctx) { |
| 125 | const auto ticket = ctx.ReadBuffer(); | 125 | const auto raw_ticket = ctx.ReadBuffer(); |
| 126 | [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); | 126 | [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); |
| 127 | 127 | ||
| 128 | if (ticket.size() < sizeof(Core::Crypto::Ticket)) { | 128 | if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) { |
| 129 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); | 129 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); |
| 130 | IPC::ResponseBuilder rb{ctx, 2}; | 130 | IPC::ResponseBuilder rb{ctx, 2}; |
| 131 | rb.Push(ERROR_INVALID_ARGUMENT); | 131 | rb.Push(ERROR_INVALID_ARGUMENT); |
| 132 | return; | 132 | return; |
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | Core::Crypto::Ticket raw{}; | 135 | Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket); |
| 136 | std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); | 136 | if (!keys.AddTicket(ticket)) { |
| 137 | |||
| 138 | if (!keys.AddTicketPersonalized(raw)) { | ||
| 139 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); | 137 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); |
| 140 | IPC::ResponseBuilder rb{ctx, 2}; | 138 | IPC::ResponseBuilder rb{ctx, 2}; |
| 141 | rb.Push(ERROR_INVALID_ARGUMENT); | 139 | rb.Push(ERROR_INVALID_ARGUMENT); |
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index ac465d5a9..508db7360 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include <utility> | 4 | #include <utility> |
| 5 | 5 | ||
| 6 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 7 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | 8 | #include "common/fs/path_util.h" |
| 8 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 9 | #include "core/core.h" | 10 | #include "core/core.h" |
| @@ -154,10 +155,18 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, | |||
| 154 | std::string src_path(Common::FS::SanitizePath(src_path_)); | 155 | std::string src_path(Common::FS::SanitizePath(src_path_)); |
| 155 | std::string dest_path(Common::FS::SanitizePath(dest_path_)); | 156 | std::string dest_path(Common::FS::SanitizePath(dest_path_)); |
| 156 | auto src = backing->GetFileRelative(src_path); | 157 | auto src = backing->GetFileRelative(src_path); |
| 158 | auto dst = backing->GetFileRelative(dest_path); | ||
| 157 | if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { | 159 | if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { |
| 158 | // Use more-optimized vfs implementation rename. | 160 | // Use more-optimized vfs implementation rename. |
| 159 | if (src == nullptr) | 161 | if (src == nullptr) { |
| 160 | return FileSys::ERROR_PATH_NOT_FOUND; | 162 | return FileSys::ERROR_PATH_NOT_FOUND; |
| 163 | } | ||
| 164 | |||
| 165 | if (dst && Common::FS::Exists(dst->GetFullPath())) { | ||
| 166 | LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath()); | ||
| 167 | return FileSys::ERROR_PATH_ALREADY_EXISTS; | ||
| 168 | } | ||
| 169 | |||
| 161 | if (!src->Rename(Common::FS::GetFilename(dest_path))) { | 170 | if (!src->Rename(Common::FS::GetFilename(dest_path))) { |
| 162 | // TODO(DarkLordZach): Find a better error code for this | 171 | // TODO(DarkLordZach): Find a better error code for this |
| 163 | return ResultUnknown; | 172 | return ResultUnknown; |
| @@ -373,6 +382,11 @@ FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::Stor | |||
| 373 | return romfs_factory->Open(title_id, storage_id, type); | 382 | return romfs_factory->Open(title_id, storage_id, type); |
| 374 | } | 383 | } |
| 375 | 384 | ||
| 385 | std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca( | ||
| 386 | u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { | ||
| 387 | return romfs_factory->GetEntry(title_id, storage_id, type); | ||
| 388 | } | ||
| 389 | |||
| 376 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, | 390 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, |
| 377 | FileSys::SaveDataSpaceId space, | 391 | FileSys::SaveDataSpaceId space, |
| 378 | const FileSys::SaveDataAttribute& save_struct) const { | 392 | const FileSys::SaveDataAttribute& save_struct) const { |
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index fd991f976..e7e7c4c28 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h | |||
| @@ -15,6 +15,7 @@ class System; | |||
| 15 | 15 | ||
| 16 | namespace FileSys { | 16 | namespace FileSys { |
| 17 | class BISFactory; | 17 | class BISFactory; |
| 18 | class NCA; | ||
| 18 | class RegisteredCache; | 19 | class RegisteredCache; |
| 19 | class RegisteredCacheUnion; | 20 | class RegisteredCacheUnion; |
| 20 | class PlaceholderCache; | 21 | class PlaceholderCache; |
| @@ -70,6 +71,8 @@ public: | |||
| 70 | FileSys::ContentRecordType type) const; | 71 | FileSys::ContentRecordType type) const; |
| 71 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, | 72 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, |
| 72 | FileSys::ContentRecordType type) const; | 73 | FileSys::ContentRecordType type) const; |
| 74 | std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id, | ||
| 75 | FileSys::ContentRecordType type) const; | ||
| 73 | 76 | ||
| 74 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, | 77 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, |
| 75 | const FileSys::SaveDataAttribute& save_struct) const; | 78 | const FileSys::SaveDataAttribute& save_struct) const; |
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 423a814cb..6e4d26b1e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp | |||
| @@ -1029,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { | |||
| 1029 | 1029 | ||
| 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; | 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; |
| 1031 | 1031 | ||
| 1032 | auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); | ||
| 1032 | auto storage = std::make_shared<IStorage>( | 1033 | auto storage = std::make_shared<IStorage>( |
| 1033 | system, pm.PatchRomFS(std::move(data), 0, FileSys::ContentRecordType::Data)); | 1034 | system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); |
| 1034 | 1035 | ||
| 1035 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 1036 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 1036 | rb.Push(ResultSuccess); | 1037 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 03432f7cb..63eecd42b 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp | |||
| @@ -331,7 +331,7 @@ Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() | |||
| 331 | }; | 331 | }; |
| 332 | 332 | ||
| 333 | // Hack: There is no touch in docked but games still allow it | 333 | // Hack: There is no touch in docked but games still allow it |
| 334 | if (Settings::values.use_docked_mode.GetValue()) { | 334 | if (Settings::IsDockedMode()) { |
| 335 | gesture.points[id] = { | 335 | gesture.points[id] = { |
| 336 | .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), | 336 | .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), |
| 337 | .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), | 337 | .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), |
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 28818c813..146bb486d 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp | |||
| @@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 193 | shared_memory->system_properties.use_minus.Assign(1); | 193 | shared_memory->system_properties.use_minus.Assign(1); |
| 194 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 194 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 195 | battery_level.dual.is_charging); | 195 | battery_level.dual.is_charging); |
| 196 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; | 196 | shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController; |
| 197 | shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); | 197 | shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); |
| 198 | break; | 198 | break; |
| 199 | case Core::HID::NpadStyleIndex::Handheld: | 199 | case Core::HID::NpadStyleIndex::Handheld: |
| @@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 216 | shared_memory->system_properties.is_charging_joy_right.Assign( | 216 | shared_memory->system_properties.is_charging_joy_right.Assign( |
| 217 | battery_level.right.is_charging); | 217 | battery_level.right.is_charging); |
| 218 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; | 218 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; |
| 219 | shared_memory->applet_nfc_xcd.applet_footer.type = | 219 | shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; |
| 220 | AppletFooterUiType::HandheldJoyConLeftJoyConRight; | ||
| 221 | shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); | 220 | shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); |
| 222 | break; | 221 | break; |
| 223 | case Core::HID::NpadStyleIndex::JoyconDual: | 222 | case Core::HID::NpadStyleIndex::JoyconDual: |
| @@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 247 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; | 246 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; |
| 248 | 247 | ||
| 249 | if (controller.is_dual_left_connected && controller.is_dual_right_connected) { | 248 | if (controller.is_dual_left_connected && controller.is_dual_right_connected) { |
| 250 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; | 249 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDual; |
| 251 | shared_memory->fullkey_color.fullkey = body_colors.left; | 250 | shared_memory->fullkey_color.fullkey = body_colors.left; |
| 252 | shared_memory->battery_level_dual = battery_level.left.battery_level; | 251 | shared_memory->battery_level_dual = battery_level.left.battery_level; |
| 253 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 252 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 254 | battery_level.left.is_charging); | 253 | battery_level.left.is_charging); |
| 255 | } else if (controller.is_dual_left_connected) { | 254 | } else if (controller.is_dual_left_connected) { |
| 256 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; | 255 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly; |
| 257 | shared_memory->fullkey_color.fullkey = body_colors.left; | 256 | shared_memory->fullkey_color.fullkey = body_colors.left; |
| 258 | shared_memory->battery_level_dual = battery_level.left.battery_level; | 257 | shared_memory->battery_level_dual = battery_level.left.battery_level; |
| 259 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 258 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 260 | battery_level.left.is_charging); | 259 | battery_level.left.is_charging); |
| 261 | } else { | 260 | } else { |
| 262 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; | 261 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly; |
| 263 | shared_memory->fullkey_color.fullkey = body_colors.right; | 262 | shared_memory->fullkey_color.fullkey = body_colors.right; |
| 264 | shared_memory->battery_level_dual = battery_level.right.battery_level; | 263 | shared_memory->battery_level_dual = battery_level.right.battery_level; |
| 265 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 264 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| @@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 278 | shared_memory->system_properties.use_minus.Assign(1); | 277 | shared_memory->system_properties.use_minus.Assign(1); |
| 279 | shared_memory->system_properties.is_charging_joy_left.Assign( | 278 | shared_memory->system_properties.is_charging_joy_left.Assign( |
| 280 | battery_level.left.is_charging); | 279 | battery_level.left.is_charging); |
| 281 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; | 280 | shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal; |
| 282 | shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); | 281 | shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); |
| 283 | break; | 282 | break; |
| 284 | case Core::HID::NpadStyleIndex::JoyconRight: | 283 | case Core::HID::NpadStyleIndex::JoyconRight: |
| @@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 293 | shared_memory->system_properties.use_plus.Assign(1); | 292 | shared_memory->system_properties.use_plus.Assign(1); |
| 294 | shared_memory->system_properties.is_charging_joy_right.Assign( | 293 | shared_memory->system_properties.is_charging_joy_right.Assign( |
| 295 | battery_level.right.is_charging); | 294 | battery_level.right.is_charging); |
| 296 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; | 295 | shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal; |
| 297 | shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); | 296 | shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); |
| 298 | break; | 297 | break; |
| 299 | case Core::HID::NpadStyleIndex::GameCube: | 298 | case Core::HID::NpadStyleIndex::GameCube: |
| @@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 314 | case Core::HID::NpadStyleIndex::SNES: | 313 | case Core::HID::NpadStyleIndex::SNES: |
| 315 | shared_memory->style_tag.lucia.Assign(1); | 314 | shared_memory->style_tag.lucia.Assign(1); |
| 316 | shared_memory->device_type.fullkey.Assign(1); | 315 | shared_memory->device_type.fullkey.Assign(1); |
| 317 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; | 316 | shared_memory->applet_footer_type = AppletFooterUiType::Lucia; |
| 318 | break; | 317 | break; |
| 319 | case Core::HID::NpadStyleIndex::N64: | 318 | case Core::HID::NpadStyleIndex::N64: |
| 320 | shared_memory->style_tag.lagoon.Assign(1); | 319 | shared_memory->style_tag.lagoon.Assign(1); |
| 321 | shared_memory->device_type.fullkey.Assign(1); | 320 | shared_memory->device_type.fullkey.Assign(1); |
| 322 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; | 321 | shared_memory->applet_footer_type = AppletFooterUiType::Lagon; |
| 323 | break; | 322 | break; |
| 324 | case Core::HID::NpadStyleIndex::SegaGenesis: | 323 | case Core::HID::NpadStyleIndex::SegaGenesis: |
| 325 | shared_memory->style_tag.lager.Assign(1); | 324 | shared_memory->style_tag.lager.Assign(1); |
| @@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { | |||
| 419 | std::scoped_lock lock{mutex}; | 418 | std::scoped_lock lock{mutex}; |
| 420 | auto& controller = GetControllerFromNpadIdType(npad_id); | 419 | auto& controller = GetControllerFromNpadIdType(npad_id); |
| 421 | const auto controller_type = controller.device->GetNpadStyleIndex(); | 420 | const auto controller_type = controller.device->GetNpadStyleIndex(); |
| 421 | |||
| 422 | if (!controller.device->IsConnected() && controller.is_connected) { | ||
| 423 | DisconnectNpad(npad_id); | ||
| 424 | return; | ||
| 425 | } | ||
| 422 | if (!controller.device->IsConnected()) { | 426 | if (!controller.device->IsConnected()) { |
| 423 | return; | 427 | return; |
| 424 | } | 428 | } |
| 429 | if (controller.device->IsConnected() && !controller.is_connected) { | ||
| 430 | InitNewlyAddedController(npad_id); | ||
| 431 | } | ||
| 425 | 432 | ||
| 426 | // This function is unique to yuzu for the turbo buttons and motion to work properly | 433 | // This function is unique to yuzu for the turbo buttons and motion to work properly |
| 427 | controller.device->StatusUpdate(); | 434 | controller.device->StatusUpdate(); |
| @@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { | |||
| 468 | pad_entry.npad_buttons.l.Assign(button_state.zl); | 475 | pad_entry.npad_buttons.l.Assign(button_state.zl); |
| 469 | pad_entry.npad_buttons.r.Assign(button_state.zr); | 476 | pad_entry.npad_buttons.r.Assign(button_state.zr); |
| 470 | } | 477 | } |
| 478 | |||
| 479 | if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) { | ||
| 480 | hid_core.SetLastActiveController(npad_id); | ||
| 481 | } | ||
| 471 | } | 482 | } |
| 472 | 483 | ||
| 473 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { | 484 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { |
| @@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { | |||
| 736 | 747 | ||
| 737 | // Once SetSupportedStyleSet is called controllers are fully initialized | 748 | // Once SetSupportedStyleSet is called controllers are fully initialized |
| 738 | is_controller_initialized = true; | 749 | is_controller_initialized = true; |
| 739 | |||
| 740 | // Connect all active controllers | ||
| 741 | for (auto& controller : controller_data) { | ||
| 742 | const auto& device = controller.device; | ||
| 743 | if (device->IsConnected()) { | ||
| 744 | AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | } | 750 | } |
| 748 | 751 | ||
| 749 | Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { | 752 | Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { |
| @@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { | |||
| 1116 | .left = {}, | 1119 | .left = {}, |
| 1117 | .right = {}, | 1120 | .right = {}, |
| 1118 | }; | 1121 | }; |
| 1119 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; | 1122 | shared_memory->applet_footer_type = AppletFooterUiType::None; |
| 1120 | 1123 | ||
| 1121 | controller.is_dual_left_connected = true; | 1124 | controller.is_dual_left_connected = true; |
| 1122 | controller.is_dual_right_connected = true; | 1125 | controller.is_dual_right_connected = true; |
| @@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() { | |||
| 1508 | return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); | 1511 | return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); |
| 1509 | } | 1512 | } |
| 1510 | 1513 | ||
| 1514 | void Controller_NPad::ApplyNpadSystemCommonPolicy() { | ||
| 1515 | Core::HID::NpadStyleTag styletag{}; | ||
| 1516 | styletag.fullkey.Assign(1); | ||
| 1517 | styletag.handheld.Assign(1); | ||
| 1518 | styletag.joycon_dual.Assign(1); | ||
| 1519 | styletag.system_ext.Assign(1); | ||
| 1520 | styletag.system.Assign(1); | ||
| 1521 | SetSupportedStyleSet(styletag); | ||
| 1522 | |||
| 1523 | SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual); | ||
| 1524 | |||
| 1525 | supported_npad_id_types.clear(); | ||
| 1526 | supported_npad_id_types.resize(10); | ||
| 1527 | supported_npad_id_types[0] = Core::HID::NpadIdType::Player1; | ||
| 1528 | supported_npad_id_types[1] = Core::HID::NpadIdType::Player2; | ||
| 1529 | supported_npad_id_types[2] = Core::HID::NpadIdType::Player3; | ||
| 1530 | supported_npad_id_types[3] = Core::HID::NpadIdType::Player4; | ||
| 1531 | supported_npad_id_types[4] = Core::HID::NpadIdType::Player5; | ||
| 1532 | supported_npad_id_types[5] = Core::HID::NpadIdType::Player6; | ||
| 1533 | supported_npad_id_types[6] = Core::HID::NpadIdType::Player7; | ||
| 1534 | supported_npad_id_types[7] = Core::HID::NpadIdType::Player8; | ||
| 1535 | supported_npad_id_types[8] = Core::HID::NpadIdType::Other; | ||
| 1536 | supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld; | ||
| 1537 | } | ||
| 1538 | |||
| 1511 | bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { | 1539 | bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { |
| 1512 | if (controller == Core::HID::NpadStyleIndex::Handheld) { | 1540 | if (controller == Core::HID::NpadStyleIndex::Handheld) { |
| 1513 | const bool support_handheld = | 1541 | const bool support_handheld = |
| @@ -1518,7 +1546,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller | |||
| 1518 | return false; | 1546 | return false; |
| 1519 | } | 1547 | } |
| 1520 | // Handheld shouldn't be supported in docked mode | 1548 | // Handheld shouldn't be supported in docked mode |
| 1521 | if (Settings::values.use_docked_mode.GetValue()) { | 1549 | if (Settings::IsDockedMode()) { |
| 1522 | return false; | 1550 | return false; |
| 1523 | } | 1551 | } |
| 1524 | 1552 | ||
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 776411261..949e58a4c 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h | |||
| @@ -190,6 +190,8 @@ public: | |||
| 190 | // Specifically for cheat engine and other features. | 190 | // Specifically for cheat engine and other features. |
| 191 | Core::HID::NpadButton GetAndResetPressState(); | 191 | Core::HID::NpadButton GetAndResetPressState(); |
| 192 | 192 | ||
| 193 | void ApplyNpadSystemCommonPolicy(); | ||
| 194 | |||
| 193 | static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); | 195 | static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); |
| 194 | static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); | 196 | static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); |
| 195 | static Result VerifyValidSixAxisSensorHandle( | 197 | static Result VerifyValidSixAxisSensorHandle( |
| @@ -360,7 +362,7 @@ private: | |||
| 360 | enum class AppletFooterUiType : u8 { | 362 | enum class AppletFooterUiType : u8 { |
| 361 | None = 0, | 363 | None = 0, |
| 362 | HandheldNone = 1, | 364 | HandheldNone = 1, |
| 363 | HandheldJoyConLeftOnly = 1, | 365 | HandheldJoyConLeftOnly = 2, |
| 364 | HandheldJoyConRightOnly = 3, | 366 | HandheldJoyConRightOnly = 3, |
| 365 | HandheldJoyConLeftJoyConRight = 4, | 367 | HandheldJoyConLeftJoyConRight = 4, |
| 366 | JoyDual = 5, | 368 | JoyDual = 5, |
| @@ -382,13 +384,6 @@ private: | |||
| 382 | Lagon = 21, | 384 | Lagon = 21, |
| 383 | }; | 385 | }; |
| 384 | 386 | ||
| 385 | struct AppletFooterUi { | ||
| 386 | AppletFooterUiAttributes attributes{}; | ||
| 387 | AppletFooterUiType type{AppletFooterUiType::None}; | ||
| 388 | INSERT_PADDING_BYTES(0x5B); // Reserved | ||
| 389 | }; | ||
| 390 | static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); | ||
| 391 | |||
| 392 | // This is nn::hid::NpadLarkType | 387 | // This is nn::hid::NpadLarkType |
| 393 | enum class NpadLarkType : u32 { | 388 | enum class NpadLarkType : u32 { |
| 394 | Invalid, | 389 | Invalid, |
| @@ -419,13 +414,6 @@ private: | |||
| 419 | U, | 414 | U, |
| 420 | }; | 415 | }; |
| 421 | 416 | ||
| 422 | struct AppletNfcXcd { | ||
| 423 | union { | ||
| 424 | AppletFooterUi applet_footer{}; | ||
| 425 | Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo; | ||
| 426 | }; | ||
| 427 | }; | ||
| 428 | |||
| 429 | // This is nn::hid::detail::NpadInternalState | 417 | // This is nn::hid::detail::NpadInternalState |
| 430 | struct NpadInternalState { | 418 | struct NpadInternalState { |
| 431 | Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; | 419 | Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; |
| @@ -452,7 +440,9 @@ private: | |||
| 452 | Core::HID::NpadBatteryLevel battery_level_dual{}; | 440 | Core::HID::NpadBatteryLevel battery_level_dual{}; |
| 453 | Core::HID::NpadBatteryLevel battery_level_left{}; | 441 | Core::HID::NpadBatteryLevel battery_level_left{}; |
| 454 | Core::HID::NpadBatteryLevel battery_level_right{}; | 442 | Core::HID::NpadBatteryLevel battery_level_right{}; |
| 455 | AppletNfcXcd applet_nfc_xcd{}; | 443 | AppletFooterUiAttributes applet_footer_attributes{}; |
| 444 | AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; | ||
| 445 | INSERT_PADDING_BYTES(0x5B); // Reserved | ||
| 456 | INSERT_PADDING_BYTES(0x20); // Unknown | 446 | INSERT_PADDING_BYTES(0x20); // Unknown |
| 457 | Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; | 447 | Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; |
| 458 | NpadLarkType lark_type_l_and_main{}; | 448 | NpadLarkType lark_type_l_and_main{}; |
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index e57a3a80e..dd00921fd 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h | |||
| @@ -16,22 +16,6 @@ class EmulatedConsole; | |||
| 16 | namespace Service::HID { | 16 | namespace Service::HID { |
| 17 | class Controller_Touchscreen final : public ControllerBase { | 17 | class Controller_Touchscreen final : public ControllerBase { |
| 18 | public: | 18 | public: |
| 19 | // This is nn::hid::TouchScreenModeForNx | ||
| 20 | enum class TouchScreenModeForNx : u8 { | ||
| 21 | UseSystemSetting, | ||
| 22 | Finger, | ||
| 23 | Heat2, | ||
| 24 | }; | ||
| 25 | |||
| 26 | // This is nn::hid::TouchScreenConfigurationForNx | ||
| 27 | struct TouchScreenConfigurationForNx { | ||
| 28 | TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; | ||
| 29 | INSERT_PADDING_BYTES_NOINIT(0x7); | ||
| 30 | INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved | ||
| 31 | }; | ||
| 32 | static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17, | ||
| 33 | "TouchScreenConfigurationForNx is an invalid size"); | ||
| 34 | |||
| 35 | explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); | 19 | explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); |
| 36 | ~Controller_Touchscreen() override; | 20 | ~Controller_Touchscreen() override; |
| 37 | 21 | ||
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 2bf1d8a27..4d70006c1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() { | |||
| 231 | return applet_resource; | 231 | return applet_resource; |
| 232 | } | 232 | } |
| 233 | 233 | ||
| 234 | Hid::Hid(Core::System& system_) | 234 | Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) |
| 235 | : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { | 235 | : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{ |
| 236 | system_, | ||
| 237 | service_name} { | ||
| 236 | // clang-format off | 238 | // clang-format off |
| 237 | static const FunctionInfo functions[] = { | 239 | static const FunctionInfo functions[] = { |
| 238 | {0, &Hid::CreateAppletResource, "CreateAppletResource"}, | 240 | {0, &Hid::CreateAppletResource, "CreateAppletResource"}, |
| @@ -2368,7 +2370,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) { | |||
| 2368 | 2370 | ||
| 2369 | void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { | 2371 | void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { |
| 2370 | IPC::RequestParser rp{ctx}; | 2372 | IPC::RequestParser rp{ctx}; |
| 2371 | const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; | 2373 | const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()}; |
| 2372 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 2374 | const auto applet_resource_user_id{rp.Pop<u64>()}; |
| 2373 | 2375 | ||
| 2374 | LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", | 2376 | LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", |
| @@ -2543,7 +2545,9 @@ public: | |||
| 2543 | 2545 | ||
| 2544 | class HidSys final : public ServiceFramework<HidSys> { | 2546 | class HidSys final : public ServiceFramework<HidSys> { |
| 2545 | public: | 2547 | public: |
| 2546 | explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { | 2548 | explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) |
| 2549 | : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"}, | ||
| 2550 | applet_resource{applet_resource_} { | ||
| 2547 | // clang-format off | 2551 | // clang-format off |
| 2548 | static const FunctionInfo functions[] = { | 2552 | static const FunctionInfo functions[] = { |
| 2549 | {31, nullptr, "SendKeyboardLockKeyEvent"}, | 2553 | {31, nullptr, "SendKeyboardLockKeyEvent"}, |
| @@ -2568,7 +2572,7 @@ public: | |||
| 2568 | {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, | 2572 | {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, |
| 2569 | {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, | 2573 | {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, |
| 2570 | {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, | 2574 | {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, |
| 2571 | {306, nullptr, "GetLastActiveNpad"}, | 2575 | {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"}, |
| 2572 | {307, nullptr, "GetNpadSystemExtStyle"}, | 2576 | {307, nullptr, "GetNpadSystemExtStyle"}, |
| 2573 | {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, | 2577 | {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, |
| 2574 | {309, nullptr, "GetNpadFullKeyGripColor"}, | 2578 | {309, nullptr, "GetNpadFullKeyGripColor"}, |
| @@ -2624,7 +2628,7 @@ public: | |||
| 2624 | {700, nullptr, "ActivateUniquePad"}, | 2628 | {700, nullptr, "ActivateUniquePad"}, |
| 2625 | {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, | 2629 | {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, |
| 2626 | {703, nullptr, "GetUniquePadIds"}, | 2630 | {703, nullptr, "GetUniquePadIds"}, |
| 2627 | {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"}, | 2631 | {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"}, |
| 2628 | {800, nullptr, "ListSixAxisSensorHandles"}, | 2632 | {800, nullptr, "ListSixAxisSensorHandles"}, |
| 2629 | {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, | 2633 | {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, |
| 2630 | {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, | 2634 | {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, |
| @@ -2650,7 +2654,7 @@ public: | |||
| 2650 | {830, nullptr, "SetNotificationLedPattern"}, | 2654 | {830, nullptr, "SetNotificationLedPattern"}, |
| 2651 | {831, nullptr, "SetNotificationLedPatternWithTimeout"}, | 2655 | {831, nullptr, "SetNotificationLedPatternWithTimeout"}, |
| 2652 | {832, nullptr, "PrepareHidsForNotificationWake"}, | 2656 | {832, nullptr, "PrepareHidsForNotificationWake"}, |
| 2653 | {850, nullptr, "IsUsbFullKeyControllerEnabled"}, | 2657 | {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"}, |
| 2654 | {851, nullptr, "EnableUsbFullKeyController"}, | 2658 | {851, nullptr, "EnableUsbFullKeyController"}, |
| 2655 | {852, nullptr, "IsUsbConnected"}, | 2659 | {852, nullptr, "IsUsbConnected"}, |
| 2656 | {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, | 2660 | {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, |
| @@ -2682,7 +2686,7 @@ public: | |||
| 2682 | {1150, nullptr, "SetTouchScreenMagnification"}, | 2686 | {1150, nullptr, "SetTouchScreenMagnification"}, |
| 2683 | {1151, nullptr, "GetTouchScreenFirmwareVersion"}, | 2687 | {1151, nullptr, "GetTouchScreenFirmwareVersion"}, |
| 2684 | {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, | 2688 | {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, |
| 2685 | {1153, nullptr, "GetTouchScreenDefaultConfiguration"}, | 2689 | {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, |
| 2686 | {1154, nullptr, "IsFirmwareAvailableForNotification"}, | 2690 | {1154, nullptr, "IsFirmwareAvailableForNotification"}, |
| 2687 | {1155, nullptr, "SetForceHandheldStyleVibration"}, | 2691 | {1155, nullptr, "SetForceHandheldStyleVibration"}, |
| 2688 | {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, | 2692 | {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, |
| @@ -2749,37 +2753,102 @@ public: | |||
| 2749 | // clang-format on | 2753 | // clang-format on |
| 2750 | 2754 | ||
| 2751 | RegisterHandlers(functions); | 2755 | RegisterHandlers(functions); |
| 2756 | |||
| 2757 | joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); | ||
| 2752 | } | 2758 | } |
| 2753 | 2759 | ||
| 2754 | private: | 2760 | private: |
| 2755 | void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { | 2761 | void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { |
| 2756 | // We already do this for homebrew so we can just stub it out | ||
| 2757 | LOG_WARNING(Service_HID, "called"); | 2762 | LOG_WARNING(Service_HID, "called"); |
| 2758 | 2763 | ||
| 2764 | GetAppletResource() | ||
| 2765 | ->GetController<Controller_NPad>(HidController::NPad) | ||
| 2766 | .ApplyNpadSystemCommonPolicy(); | ||
| 2767 | |||
| 2759 | IPC::ResponseBuilder rb{ctx, 2}; | 2768 | IPC::ResponseBuilder rb{ctx, 2}; |
| 2760 | rb.Push(ResultSuccess); | 2769 | rb.Push(ResultSuccess); |
| 2761 | } | 2770 | } |
| 2762 | 2771 | ||
| 2772 | void GetLastActiveNpad(HLERequestContext& ctx) { | ||
| 2773 | LOG_DEBUG(Service_HID, "(STUBBED) called"); | ||
| 2774 | |||
| 2775 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 2776 | rb.Push(ResultSuccess); | ||
| 2777 | rb.PushEnum(system.HIDCore().GetLastActiveController()); | ||
| 2778 | } | ||
| 2779 | |||
| 2763 | void GetUniquePadsFromNpad(HLERequestContext& ctx) { | 2780 | void GetUniquePadsFromNpad(HLERequestContext& ctx) { |
| 2764 | IPC::RequestParser rp{ctx}; | 2781 | IPC::RequestParser rp{ctx}; |
| 2765 | const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; | 2782 | const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; |
| 2766 | 2783 | ||
| 2767 | const s64 total_entries = 0; | ||
| 2768 | LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); | 2784 | LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); |
| 2769 | 2785 | ||
| 2786 | const std::vector<Core::HID::UniquePadId> unique_pads{}; | ||
| 2787 | |||
| 2788 | ctx.WriteBuffer(unique_pads); | ||
| 2789 | |||
| 2790 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 2791 | rb.Push(ResultSuccess); | ||
| 2792 | rb.Push(static_cast<u32>(unique_pads.size())); | ||
| 2793 | } | ||
| 2794 | |||
| 2795 | void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) { | ||
| 2796 | LOG_INFO(Service_AM, "called"); | ||
| 2797 | |||
| 2798 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 2799 | rb.Push(ResultSuccess); | ||
| 2800 | rb.PushCopyObjects(joy_detach_event->GetReadableEvent()); | ||
| 2801 | } | ||
| 2802 | |||
| 2803 | void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { | ||
| 2804 | const bool is_enabled = false; | ||
| 2805 | |||
| 2806 | LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled); | ||
| 2807 | |||
| 2770 | IPC::ResponseBuilder rb{ctx, 3}; | 2808 | IPC::ResponseBuilder rb{ctx, 3}; |
| 2771 | rb.Push(ResultSuccess); | 2809 | rb.Push(ResultSuccess); |
| 2772 | rb.Push(total_entries); | 2810 | rb.Push(is_enabled); |
| 2773 | } | 2811 | } |
| 2812 | |||
| 2813 | void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { | ||
| 2814 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2815 | |||
| 2816 | Core::HID::TouchScreenConfigurationForNx touchscreen_config{ | ||
| 2817 | .mode = Core::HID::TouchScreenModeForNx::Finger, | ||
| 2818 | }; | ||
| 2819 | |||
| 2820 | if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 && | ||
| 2821 | touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) { | ||
| 2822 | touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting; | ||
| 2823 | } | ||
| 2824 | |||
| 2825 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 2826 | rb.Push(ResultSuccess); | ||
| 2827 | rb.PushRaw(touchscreen_config); | ||
| 2828 | } | ||
| 2829 | |||
| 2830 | std::shared_ptr<IAppletResource> GetAppletResource() { | ||
| 2831 | if (applet_resource == nullptr) { | ||
| 2832 | applet_resource = std::make_shared<IAppletResource>(system, service_context); | ||
| 2833 | } | ||
| 2834 | |||
| 2835 | return applet_resource; | ||
| 2836 | } | ||
| 2837 | |||
| 2838 | Kernel::KEvent* joy_detach_event; | ||
| 2839 | KernelHelpers::ServiceContext service_context; | ||
| 2840 | std::shared_ptr<IAppletResource> applet_resource; | ||
| 2774 | }; | 2841 | }; |
| 2775 | 2842 | ||
| 2776 | void LoopProcess(Core::System& system) { | 2843 | void LoopProcess(Core::System& system) { |
| 2777 | auto server_manager = std::make_unique<ServerManager>(system); | 2844 | auto server_manager = std::make_unique<ServerManager>(system); |
| 2845 | std::shared_ptr<IAppletResource> applet_resource; | ||
| 2778 | 2846 | ||
| 2779 | server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); | 2847 | server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource)); |
| 2780 | server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); | 2848 | server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); |
| 2781 | server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); | 2849 | server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); |
| 2782 | server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); | 2850 | server_manager->RegisterNamedService("hid:sys", |
| 2851 | std::make_shared<HidSys>(system, applet_resource)); | ||
| 2783 | 2852 | ||
| 2784 | server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); | 2853 | server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); |
| 2785 | server_manager->RegisterNamedService("irs:sys", | 2854 | server_manager->RegisterNamedService("irs:sys", |
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index f247b83c2..0ca43de93 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h | |||
| @@ -95,7 +95,7 @@ private: | |||
| 95 | 95 | ||
| 96 | class Hid final : public ServiceFramework<Hid> { | 96 | class Hid final : public ServiceFramework<Hid> { |
| 97 | public: | 97 | public: |
| 98 | explicit Hid(Core::System& system_); | 98 | explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_); |
| 99 | ~Hid() override; | 99 | ~Hid() override; |
| 100 | 100 | ||
| 101 | std::shared_ptr<IAppletResource> GetAppletResource(); | 101 | std::shared_ptr<IAppletResource> GetAppletResource(); |
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 65c11a2f3..3b83c5ed7 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp | |||
| @@ -7,17 +7,16 @@ | |||
| 7 | #include "core/hle/service/ipc_helpers.h" | 7 | #include "core/hle/service/ipc_helpers.h" |
| 8 | #include "core/hle/service/mii/mii.h" | 8 | #include "core/hle/service/mii/mii.h" |
| 9 | #include "core/hle/service/mii/mii_manager.h" | 9 | #include "core/hle/service/mii/mii_manager.h" |
| 10 | #include "core/hle/service/mii/mii_result.h" | ||
| 10 | #include "core/hle/service/server_manager.h" | 11 | #include "core/hle/service/server_manager.h" |
| 11 | #include "core/hle/service/service.h" | 12 | #include "core/hle/service/service.h" |
| 12 | 13 | ||
| 13 | namespace Service::Mii { | 14 | namespace Service::Mii { |
| 14 | 15 | ||
| 15 | constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; | ||
| 16 | |||
| 17 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { | 16 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { |
| 18 | public: | 17 | public: |
| 19 | explicit IDatabaseService(Core::System& system_) | 18 | explicit IDatabaseService(Core::System& system_, bool is_system_) |
| 20 | : ServiceFramework{system_, "IDatabaseService"} { | 19 | : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} { |
| 21 | // clang-format off | 20 | // clang-format off |
| 22 | static const FunctionInfo functions[] = { | 21 | static const FunctionInfo functions[] = { |
| 23 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, | 22 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, |
| @@ -54,34 +53,27 @@ public: | |||
| 54 | } | 53 | } |
| 55 | 54 | ||
| 56 | private: | 55 | private: |
| 57 | template <typename T> | ||
| 58 | std::vector<u8> SerializeArray(const std::vector<T>& values) { | ||
| 59 | std::vector<u8> out(values.size() * sizeof(T)); | ||
| 60 | std::size_t offset{}; | ||
| 61 | for (const auto& value : values) { | ||
| 62 | std::memcpy(out.data() + offset, &value, sizeof(T)); | ||
| 63 | offset += sizeof(T); | ||
| 64 | } | ||
| 65 | return out; | ||
| 66 | } | ||
| 67 | |||
| 68 | void IsUpdated(HLERequestContext& ctx) { | 56 | void IsUpdated(HLERequestContext& ctx) { |
| 69 | IPC::RequestParser rp{ctx}; | 57 | IPC::RequestParser rp{ctx}; |
| 70 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 58 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 71 | 59 | ||
| 72 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 60 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 73 | 61 | ||
| 62 | const bool is_updated = manager.IsUpdated(metadata, source_flag); | ||
| 63 | |||
| 74 | IPC::ResponseBuilder rb{ctx, 3}; | 64 | IPC::ResponseBuilder rb{ctx, 3}; |
| 75 | rb.Push(ResultSuccess); | 65 | rb.Push(ResultSuccess); |
| 76 | rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); | 66 | rb.Push<u8>(is_updated); |
| 77 | } | 67 | } |
| 78 | 68 | ||
| 79 | void IsFullDatabase(HLERequestContext& ctx) { | 69 | void IsFullDatabase(HLERequestContext& ctx) { |
| 80 | LOG_DEBUG(Service_Mii, "called"); | 70 | LOG_DEBUG(Service_Mii, "called"); |
| 81 | 71 | ||
| 72 | const bool is_full_database = manager.IsFullDatabase(); | ||
| 73 | |||
| 82 | IPC::ResponseBuilder rb{ctx, 3}; | 74 | IPC::ResponseBuilder rb{ctx, 3}; |
| 83 | rb.Push(ResultSuccess); | 75 | rb.Push(ResultSuccess); |
| 84 | rb.Push(manager.IsFullDatabase()); | 76 | rb.Push<u8>(is_full_database); |
| 85 | } | 77 | } |
| 86 | 78 | ||
| 87 | void GetCount(HLERequestContext& ctx) { | 79 | void GetCount(HLERequestContext& ctx) { |
| @@ -90,57 +82,63 @@ private: | |||
| 90 | 82 | ||
| 91 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 83 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 92 | 84 | ||
| 85 | const u32 mii_count = manager.GetCount(metadata, source_flag); | ||
| 86 | |||
| 93 | IPC::ResponseBuilder rb{ctx, 3}; | 87 | IPC::ResponseBuilder rb{ctx, 3}; |
| 94 | rb.Push(ResultSuccess); | 88 | rb.Push(ResultSuccess); |
| 95 | rb.Push<u32>(manager.GetCount(source_flag)); | 89 | rb.Push(mii_count); |
| 96 | } | 90 | } |
| 97 | 91 | ||
| 98 | void Get(HLERequestContext& ctx) { | 92 | void Get(HLERequestContext& ctx) { |
| 99 | IPC::RequestParser rp{ctx}; | 93 | IPC::RequestParser rp{ctx}; |
| 100 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 94 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 95 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; | ||
| 101 | 96 | ||
| 102 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 97 | LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); |
| 98 | |||
| 99 | u32 mii_count{}; | ||
| 100 | std::vector<CharInfoElement> char_info_elements(output_size); | ||
| 101 | Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag); | ||
| 103 | 102 | ||
| 104 | const auto default_miis{manager.GetDefault(source_flag)}; | 103 | if (mii_count != 0) { |
| 105 | if (default_miis.size() > 0) { | 104 | ctx.WriteBuffer(char_info_elements); |
| 106 | ctx.WriteBuffer(SerializeArray(default_miis)); | ||
| 107 | } | 105 | } |
| 108 | 106 | ||
| 109 | IPC::ResponseBuilder rb{ctx, 3}; | 107 | IPC::ResponseBuilder rb{ctx, 3}; |
| 110 | rb.Push(ResultSuccess); | 108 | rb.Push(result); |
| 111 | rb.Push<u32>(static_cast<u32>(default_miis.size())); | 109 | rb.Push(mii_count); |
| 112 | } | 110 | } |
| 113 | 111 | ||
| 114 | void Get1(HLERequestContext& ctx) { | 112 | void Get1(HLERequestContext& ctx) { |
| 115 | IPC::RequestParser rp{ctx}; | 113 | IPC::RequestParser rp{ctx}; |
| 116 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 114 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 115 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; | ||
| 117 | 116 | ||
| 118 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 117 | LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); |
| 119 | 118 | ||
| 120 | const auto default_miis{manager.GetDefault(source_flag)}; | 119 | u32 mii_count{}; |
| 120 | std::vector<CharInfo> char_info(output_size); | ||
| 121 | Result result = manager.Get(metadata, char_info, mii_count, source_flag); | ||
| 121 | 122 | ||
| 122 | std::vector<CharInfo> values; | 123 | if (mii_count != 0) { |
| 123 | for (const auto& element : default_miis) { | 124 | ctx.WriteBuffer(char_info); |
| 124 | values.emplace_back(element.info); | ||
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | ctx.WriteBuffer(SerializeArray(values)); | ||
| 128 | |||
| 129 | IPC::ResponseBuilder rb{ctx, 3}; | 127 | IPC::ResponseBuilder rb{ctx, 3}; |
| 130 | rb.Push(ResultSuccess); | 128 | rb.Push(result); |
| 131 | rb.Push<u32>(static_cast<u32>(default_miis.size())); | 129 | rb.Push(mii_count); |
| 132 | } | 130 | } |
| 133 | 131 | ||
| 134 | void UpdateLatest(HLERequestContext& ctx) { | 132 | void UpdateLatest(HLERequestContext& ctx) { |
| 135 | IPC::RequestParser rp{ctx}; | 133 | IPC::RequestParser rp{ctx}; |
| 136 | const auto info{rp.PopRaw<CharInfo>()}; | 134 | const auto char_info{rp.PopRaw<CharInfo>()}; |
| 137 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 135 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 138 | 136 | ||
| 139 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 137 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 140 | 138 | ||
| 141 | CharInfo new_char_info{}; | 139 | CharInfo new_char_info{}; |
| 142 | const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)}; | 140 | const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); |
| 143 | if (result != ResultSuccess) { | 141 | if (result.IsFailure()) { |
| 144 | IPC::ResponseBuilder rb{ctx, 2}; | 142 | IPC::ResponseBuilder rb{ctx, 2}; |
| 145 | rb.Push(result); | 143 | rb.Push(result); |
| 146 | return; | 144 | return; |
| @@ -153,7 +151,6 @@ private: | |||
| 153 | 151 | ||
| 154 | void BuildRandom(HLERequestContext& ctx) { | 152 | void BuildRandom(HLERequestContext& ctx) { |
| 155 | IPC::RequestParser rp{ctx}; | 153 | IPC::RequestParser rp{ctx}; |
| 156 | |||
| 157 | const auto age{rp.PopRaw<Age>()}; | 154 | const auto age{rp.PopRaw<Age>()}; |
| 158 | const auto gender{rp.PopRaw<Gender>()}; | 155 | const auto gender{rp.PopRaw<Gender>()}; |
| 159 | const auto race{rp.PopRaw<Race>()}; | 156 | const auto race{rp.PopRaw<Race>()}; |
| @@ -162,47 +159,48 @@ private: | |||
| 162 | 159 | ||
| 163 | if (age > Age::All) { | 160 | if (age > Age::All) { |
| 164 | IPC::ResponseBuilder rb{ctx, 2}; | 161 | IPC::ResponseBuilder rb{ctx, 2}; |
| 165 | rb.Push(ERROR_INVALID_ARGUMENT); | 162 | rb.Push(ResultInvalidArgument); |
| 166 | LOG_ERROR(Service_Mii, "invalid age={}", age); | ||
| 167 | return; | 163 | return; |
| 168 | } | 164 | } |
| 169 | 165 | ||
| 170 | if (gender > Gender::All) { | 166 | if (gender > Gender::All) { |
| 171 | IPC::ResponseBuilder rb{ctx, 2}; | 167 | IPC::ResponseBuilder rb{ctx, 2}; |
| 172 | rb.Push(ERROR_INVALID_ARGUMENT); | 168 | rb.Push(ResultInvalidArgument); |
| 173 | LOG_ERROR(Service_Mii, "invalid gender={}", gender); | ||
| 174 | return; | 169 | return; |
| 175 | } | 170 | } |
| 176 | 171 | ||
| 177 | if (race > Race::All) { | 172 | if (race > Race::All) { |
| 178 | IPC::ResponseBuilder rb{ctx, 2}; | 173 | IPC::ResponseBuilder rb{ctx, 2}; |
| 179 | rb.Push(ERROR_INVALID_ARGUMENT); | 174 | rb.Push(ResultInvalidArgument); |
| 180 | LOG_ERROR(Service_Mii, "invalid race={}", race); | ||
| 181 | return; | 175 | return; |
| 182 | } | 176 | } |
| 183 | 177 | ||
| 178 | CharInfo char_info{}; | ||
| 179 | manager.BuildRandom(char_info, age, gender, race); | ||
| 180 | |||
| 184 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 181 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 185 | rb.Push(ResultSuccess); | 182 | rb.Push(ResultSuccess); |
| 186 | rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); | 183 | rb.PushRaw<CharInfo>(char_info); |
| 187 | } | 184 | } |
| 188 | 185 | ||
| 189 | void BuildDefault(HLERequestContext& ctx) { | 186 | void BuildDefault(HLERequestContext& ctx) { |
| 190 | IPC::RequestParser rp{ctx}; | 187 | IPC::RequestParser rp{ctx}; |
| 191 | const auto index{rp.Pop<u32>()}; | 188 | const auto index{rp.Pop<u32>()}; |
| 192 | 189 | ||
| 193 | LOG_DEBUG(Service_Mii, "called with index={}", index); | 190 | LOG_INFO(Service_Mii, "called with index={}", index); |
| 194 | 191 | ||
| 195 | if (index > 5) { | 192 | if (index > 5) { |
| 196 | LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}", | ||
| 197 | index); | ||
| 198 | IPC::ResponseBuilder rb{ctx, 2}; | 193 | IPC::ResponseBuilder rb{ctx, 2}; |
| 199 | rb.Push(ERROR_INVALID_ARGUMENT); | 194 | rb.Push(ResultInvalidArgument); |
| 200 | return; | 195 | return; |
| 201 | } | 196 | } |
| 202 | 197 | ||
| 198 | CharInfo char_info{}; | ||
| 199 | manager.BuildDefault(char_info, index); | ||
| 200 | |||
| 203 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 201 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 204 | rb.Push(ResultSuccess); | 202 | rb.Push(ResultSuccess); |
| 205 | rb.PushRaw<CharInfo>(manager.BuildDefault(index)); | 203 | rb.PushRaw<CharInfo>(char_info); |
| 206 | } | 204 | } |
| 207 | 205 | ||
| 208 | void GetIndex(HLERequestContext& ctx) { | 206 | void GetIndex(HLERequestContext& ctx) { |
| @@ -211,19 +209,21 @@ private: | |||
| 211 | 209 | ||
| 212 | LOG_DEBUG(Service_Mii, "called"); | 210 | LOG_DEBUG(Service_Mii, "called"); |
| 213 | 211 | ||
| 214 | u32 index{}; | 212 | s32 index{}; |
| 213 | const auto result = manager.GetIndex(metadata, info, index); | ||
| 214 | |||
| 215 | IPC::ResponseBuilder rb{ctx, 3}; | 215 | IPC::ResponseBuilder rb{ctx, 3}; |
| 216 | rb.Push(manager.GetIndex(info, index)); | 216 | rb.Push(result); |
| 217 | rb.Push(index); | 217 | rb.Push(index); |
| 218 | } | 218 | } |
| 219 | 219 | ||
| 220 | void SetInterfaceVersion(HLERequestContext& ctx) { | 220 | void SetInterfaceVersion(HLERequestContext& ctx) { |
| 221 | IPC::RequestParser rp{ctx}; | 221 | IPC::RequestParser rp{ctx}; |
| 222 | current_interface_version = rp.PopRaw<u32>(); | 222 | const auto interface_version{rp.PopRaw<u32>()}; |
| 223 | 223 | ||
| 224 | LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); | 224 | LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); |
| 225 | 225 | ||
| 226 | UNIMPLEMENTED_IF(current_interface_version != 1); | 226 | manager.SetInterfaceVersion(metadata, interface_version); |
| 227 | 227 | ||
| 228 | IPC::ResponseBuilder rb{ctx, 2}; | 228 | IPC::ResponseBuilder rb{ctx, 2}; |
| 229 | rb.Push(ResultSuccess); | 229 | rb.Push(ResultSuccess); |
| @@ -231,30 +231,27 @@ private: | |||
| 231 | 231 | ||
| 232 | void Convert(HLERequestContext& ctx) { | 232 | void Convert(HLERequestContext& ctx) { |
| 233 | IPC::RequestParser rp{ctx}; | 233 | IPC::RequestParser rp{ctx}; |
| 234 | |||
| 235 | const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; | 234 | const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; |
| 236 | 235 | ||
| 237 | LOG_INFO(Service_Mii, "called"); | 236 | LOG_INFO(Service_Mii, "called"); |
| 238 | 237 | ||
| 238 | CharInfo char_info{}; | ||
| 239 | manager.ConvertV3ToCharInfo(char_info, mii_v3); | ||
| 240 | |||
| 239 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 241 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 240 | rb.Push(ResultSuccess); | 242 | rb.Push(ResultSuccess); |
| 241 | rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); | 243 | rb.PushRaw<CharInfo>(char_info); |
| 242 | } | ||
| 243 | |||
| 244 | constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { | ||
| 245 | return current_interface_version >= interface_version; | ||
| 246 | } | 244 | } |
| 247 | 245 | ||
| 248 | MiiManager manager; | 246 | MiiManager manager{}; |
| 249 | 247 | DatabaseSessionMetadata metadata{}; | |
| 250 | u32 current_interface_version{}; | 248 | bool is_system{}; |
| 251 | u64 current_update_counter{}; | ||
| 252 | }; | 249 | }; |
| 253 | 250 | ||
| 254 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { | 251 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { |
| 255 | public: | 252 | public: |
| 256 | explicit MiiDBModule(Core::System& system_, const char* name_) | 253 | explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_) |
| 257 | : ServiceFramework{system_, name_} { | 254 | : ServiceFramework{system_, name_}, is_system{is_system_} { |
| 258 | // clang-format off | 255 | // clang-format off |
| 259 | static const FunctionInfo functions[] = { | 256 | static const FunctionInfo functions[] = { |
| 260 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, | 257 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, |
| @@ -268,10 +265,12 @@ private: | |||
| 268 | void GetDatabaseService(HLERequestContext& ctx) { | 265 | void GetDatabaseService(HLERequestContext& ctx) { |
| 269 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 266 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 270 | rb.Push(ResultSuccess); | 267 | rb.Push(ResultSuccess); |
| 271 | rb.PushIpcInterface<IDatabaseService>(system); | 268 | rb.PushIpcInterface<IDatabaseService>(system, is_system); |
| 272 | 269 | ||
| 273 | LOG_DEBUG(Service_Mii, "called"); | 270 | LOG_DEBUG(Service_Mii, "called"); |
| 274 | } | 271 | } |
| 272 | |||
| 273 | bool is_system{}; | ||
| 275 | }; | 274 | }; |
| 276 | 275 | ||
| 277 | class MiiImg final : public ServiceFramework<MiiImg> { | 276 | class MiiImg final : public ServiceFramework<MiiImg> { |
| @@ -303,8 +302,10 @@ public: | |||
| 303 | void LoopProcess(Core::System& system) { | 302 | void LoopProcess(Core::System& system) { |
| 304 | auto server_manager = std::make_unique<ServerManager>(system); | 303 | auto server_manager = std::make_unique<ServerManager>(system); |
| 305 | 304 | ||
| 306 | server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e")); | 305 | server_manager->RegisterNamedService("mii:e", |
| 307 | server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u")); | 306 | std::make_shared<MiiDBModule>(system, "mii:e", true)); |
| 307 | server_manager->RegisterNamedService("mii:u", | ||
| 308 | std::make_shared<MiiDBModule>(system, "mii:u", false)); | ||
| 308 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); | 309 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); |
| 309 | ServerManager::RunServer(std::move(server_manager)); | 310 | ServerManager::RunServer(std::move(server_manager)); |
| 310 | } | 311 | } |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 46125d473..292d63777 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -10,386 +10,24 @@ | |||
| 10 | 10 | ||
| 11 | #include "core/hle/service/acc/profile_manager.h" | 11 | #include "core/hle/service/acc/profile_manager.h" |
| 12 | #include "core/hle/service/mii/mii_manager.h" | 12 | #include "core/hle/service/mii/mii_manager.h" |
| 13 | #include "core/hle/service/mii/raw_data.h" | 13 | #include "core/hle/service/mii/mii_result.h" |
| 14 | #include "core/hle/service/mii/mii_util.h" | ||
| 15 | #include "core/hle/service/mii/types/core_data.h" | ||
| 16 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 14 | 17 | ||
| 15 | namespace Service::Mii { | 18 | namespace Service::Mii { |
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; | ||
| 20 | |||
| 21 | constexpr std::size_t BaseMiiCount{2}; | ||
| 22 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; | 19 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; |
| 23 | 20 | ||
| 24 | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; | 21 | MiiManager::MiiManager() {} |
| 25 | constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; | ||
| 26 | constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; | ||
| 27 | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; | ||
| 28 | constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; | ||
| 29 | constexpr std::array<u8, 62> EyeRotateLookup{ | ||
| 30 | {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, | ||
| 31 | 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||
| 32 | 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, | ||
| 33 | 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; | ||
| 34 | constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, | ||
| 35 | 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, | ||
| 36 | 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; | ||
| 37 | |||
| 38 | template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> | ||
| 39 | std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) { | ||
| 40 | std::array<T, DestArraySize> out{}; | ||
| 41 | std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); | ||
| 42 | return out; | ||
| 43 | } | ||
| 44 | |||
| 45 | CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) { | ||
| 46 | MiiStoreBitFields bf; | ||
| 47 | std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); | ||
| 48 | |||
| 49 | return { | ||
| 50 | .uuid = data.data.uuid, | ||
| 51 | .name = ResizeArray<char16_t, 10, 11>(data.data.name), | ||
| 52 | .font_region = static_cast<u8>(bf.font_region.Value()), | ||
| 53 | .favorite_color = static_cast<u8>(bf.favorite_color.Value()), | ||
| 54 | .gender = static_cast<u8>(bf.gender.Value()), | ||
| 55 | .height = static_cast<u8>(bf.height.Value()), | ||
| 56 | .build = static_cast<u8>(bf.build.Value()), | ||
| 57 | .type = static_cast<u8>(bf.type.Value()), | ||
| 58 | .region_move = static_cast<u8>(bf.region_move.Value()), | ||
| 59 | .faceline_type = static_cast<u8>(bf.faceline_type.Value()), | ||
| 60 | .faceline_color = static_cast<u8>(bf.faceline_color.Value()), | ||
| 61 | .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()), | ||
| 62 | .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()), | ||
| 63 | .hair_type = static_cast<u8>(bf.hair_type.Value()), | ||
| 64 | .hair_color = static_cast<u8>(bf.hair_color.Value()), | ||
| 65 | .hair_flip = static_cast<u8>(bf.hair_flip.Value()), | ||
| 66 | .eye_type = static_cast<u8>(bf.eye_type.Value()), | ||
| 67 | .eye_color = static_cast<u8>(bf.eye_color.Value()), | ||
| 68 | .eye_scale = static_cast<u8>(bf.eye_scale.Value()), | ||
| 69 | .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()), | ||
| 70 | .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()), | ||
| 71 | .eye_x = static_cast<u8>(bf.eye_x.Value()), | ||
| 72 | .eye_y = static_cast<u8>(bf.eye_y.Value()), | ||
| 73 | .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()), | ||
| 74 | .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()), | ||
| 75 | .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()), | ||
| 76 | .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()), | ||
| 77 | .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()), | ||
| 78 | .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()), | ||
| 79 | .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3), | ||
| 80 | .nose_type = static_cast<u8>(bf.nose_type.Value()), | ||
| 81 | .nose_scale = static_cast<u8>(bf.nose_scale.Value()), | ||
| 82 | .nose_y = static_cast<u8>(bf.nose_y.Value()), | ||
| 83 | .mouth_type = static_cast<u8>(bf.mouth_type.Value()), | ||
| 84 | .mouth_color = static_cast<u8>(bf.mouth_color.Value()), | ||
| 85 | .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()), | ||
| 86 | .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()), | ||
| 87 | .mouth_y = static_cast<u8>(bf.mouth_y.Value()), | ||
| 88 | .beard_color = static_cast<u8>(bf.beard_color.Value()), | ||
| 89 | .beard_type = static_cast<u8>(bf.beard_type.Value()), | ||
| 90 | .mustache_type = static_cast<u8>(bf.mustache_type.Value()), | ||
| 91 | .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()), | ||
| 92 | .mustache_y = static_cast<u8>(bf.mustache_y.Value()), | ||
| 93 | .glasses_type = static_cast<u8>(bf.glasses_type.Value()), | ||
| 94 | .glasses_color = static_cast<u8>(bf.glasses_color.Value()), | ||
| 95 | .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()), | ||
| 96 | .glasses_y = static_cast<u8>(bf.glasses_y.Value()), | ||
| 97 | .mole_type = static_cast<u8>(bf.mole_type.Value()), | ||
| 98 | .mole_scale = static_cast<u8>(bf.mole_scale.Value()), | ||
| 99 | .mole_x = static_cast<u8>(bf.mole_x.Value()), | ||
| 100 | .mole_y = static_cast<u8>(bf.mole_y.Value()), | ||
| 101 | .padding = 0, | ||
| 102 | }; | ||
| 103 | } | ||
| 104 | |||
| 105 | u16 GenerateCrc16(const void* data, std::size_t size) { | ||
| 106 | s32 crc{}; | ||
| 107 | for (std::size_t i = 0; i < size; i++) { | ||
| 108 | crc ^= static_cast<const u8*>(data)[i] << 8; | ||
| 109 | for (std::size_t j = 0; j < 8; j++) { | ||
| 110 | crc <<= 1; | ||
| 111 | if ((crc & 0x10000) != 0) { | ||
| 112 | crc = (crc ^ 0x1021) & 0xFFFF; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | return Common::swap16(static_cast<u16>(crc)); | ||
| 117 | } | ||
| 118 | |||
| 119 | template <typename T> | ||
| 120 | T GetRandomValue(T min, T max) { | ||
| 121 | std::random_device device; | ||
| 122 | std::mt19937 gen(device()); | ||
| 123 | std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max)); | ||
| 124 | return static_cast<T>(distribution(gen)); | ||
| 125 | } | ||
| 126 | |||
| 127 | template <typename T> | ||
| 128 | T GetRandomValue(T max) { | ||
| 129 | return GetRandomValue<T>({}, max); | ||
| 130 | } | ||
| 131 | |||
| 132 | MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { | ||
| 133 | MiiStoreBitFields bf{}; | ||
| 134 | |||
| 135 | if (gender == Gender::All) { | ||
| 136 | gender = GetRandomValue<Gender>(Gender::Maximum); | ||
| 137 | } | ||
| 138 | |||
| 139 | bf.gender.Assign(gender); | ||
| 140 | bf.favorite_color.Assign(GetRandomValue<u8>(11)); | ||
| 141 | bf.region_move.Assign(0); | ||
| 142 | bf.font_region.Assign(FontRegion::Standard); | ||
| 143 | bf.type.Assign(0); | ||
| 144 | bf.height.Assign(64); | ||
| 145 | bf.build.Assign(64); | ||
| 146 | |||
| 147 | if (age == Age::All) { | ||
| 148 | const auto temp{GetRandomValue<int>(10)}; | ||
| 149 | if (temp >= 8) { | ||
| 150 | age = Age::Old; | ||
| 151 | } else if (temp >= 4) { | ||
| 152 | age = Age::Normal; | ||
| 153 | } else { | ||
| 154 | age = Age::Young; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | if (race == Race::All) { | ||
| 159 | const auto temp{GetRandomValue<int>(10)}; | ||
| 160 | if (temp >= 8) { | ||
| 161 | race = Race::Black; | ||
| 162 | } else if (temp >= 4) { | ||
| 163 | race = Race::White; | ||
| 164 | } else { | ||
| 165 | race = Race::Asian; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | u32 axis_y{}; | ||
| 170 | if (gender == Gender::Female && age == Age::Young) { | ||
| 171 | axis_y = GetRandomValue<u32>(3); | ||
| 172 | } | ||
| 173 | |||
| 174 | const std::size_t index{3 * static_cast<std::size_t>(age) + | ||
| 175 | 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; | ||
| 176 | |||
| 177 | const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; | ||
| 178 | const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( | ||
| 179 | 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; | ||
| 180 | const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; | ||
| 181 | const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; | ||
| 182 | const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; | ||
| 183 | const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + | ||
| 184 | static_cast<std::size_t>(age))}; | ||
| 185 | const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; | ||
| 186 | const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; | ||
| 187 | const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; | ||
| 188 | const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; | ||
| 189 | const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; | ||
| 190 | const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; | ||
| 191 | |||
| 192 | bf.faceline_type.Assign( | ||
| 193 | faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]); | ||
| 194 | bf.faceline_color.Assign( | ||
| 195 | faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]); | ||
| 196 | bf.faceline_wrinkle.Assign( | ||
| 197 | faceline_wrinkle_info | ||
| 198 | .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); | ||
| 199 | bf.faceline_makeup.Assign( | ||
| 200 | faceline_makeup_info | ||
| 201 | .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); | ||
| 202 | |||
| 203 | bf.hair_type.Assign( | ||
| 204 | hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]); | ||
| 205 | bf.hair_color.Assign( | ||
| 206 | HairColorLookup[hair_color_info | ||
| 207 | .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]); | ||
| 208 | bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum)); | ||
| 209 | |||
| 210 | bf.eye_type.Assign( | ||
| 211 | eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]); | ||
| 212 | |||
| 213 | const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; | ||
| 214 | const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; | ||
| 215 | const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; | ||
| 216 | const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; | ||
| 217 | |||
| 218 | bf.eye_color.Assign( | ||
| 219 | EyeColorLookup[eye_color_info | ||
| 220 | .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]); | ||
| 221 | bf.eye_scale.Assign(4); | ||
| 222 | bf.eye_aspect.Assign(3); | ||
| 223 | bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); | ||
| 224 | bf.eye_x.Assign(2); | ||
| 225 | bf.eye_y.Assign(axis_y + 12); | ||
| 226 | |||
| 227 | bf.eyebrow_type.Assign( | ||
| 228 | eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | ||
| 229 | |||
| 230 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | ||
| 231 | const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | ||
| 232 | const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; | ||
| 233 | const auto eyebrow_rotate{ | ||
| 234 | 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]}; | ||
| 235 | |||
| 236 | bf.eyebrow_color.Assign(bf.hair_color); | ||
| 237 | bf.eyebrow_scale.Assign(4); | ||
| 238 | bf.eyebrow_aspect.Assign(3); | ||
| 239 | bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); | ||
| 240 | bf.eyebrow_x.Assign(2); | ||
| 241 | bf.eyebrow_y.Assign(axis_y + eyebrow_y); | ||
| 242 | |||
| 243 | const auto nose_scale{gender == Gender::Female ? 3 : 4}; | ||
| 244 | |||
| 245 | bf.nose_type.Assign( | ||
| 246 | nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]); | ||
| 247 | bf.nose_scale.Assign(nose_scale); | ||
| 248 | bf.nose_y.Assign(axis_y + 9); | ||
| 249 | |||
| 250 | const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0}; | ||
| 251 | |||
| 252 | bf.mouth_type.Assign( | ||
| 253 | mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]); | ||
| 254 | bf.mouth_color.Assign(MouthColorLookup[mouth_color]); | ||
| 255 | bf.mouth_scale.Assign(4); | ||
| 256 | bf.mouth_aspect.Assign(3); | ||
| 257 | bf.mouth_y.Assign(axis_y + 13); | ||
| 258 | |||
| 259 | bf.beard_color.Assign(bf.hair_color); | ||
| 260 | bf.mustache_scale.Assign(4); | ||
| 261 | |||
| 262 | if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) { | ||
| 263 | const auto mustache_and_beard_flag{ | ||
| 264 | GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)}; | ||
| 265 | |||
| 266 | auto beard_type{BeardType::None}; | ||
| 267 | auto mustache_type{MustacheType::None}; | ||
| 268 | |||
| 269 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == | ||
| 270 | BeardAndMustacheFlag::Beard) { | ||
| 271 | beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5); | ||
| 272 | } | ||
| 273 | |||
| 274 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == | ||
| 275 | BeardAndMustacheFlag::Mustache) { | ||
| 276 | mustache_type = | ||
| 277 | GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5); | ||
| 278 | } | ||
| 279 | |||
| 280 | bf.mustache_type.Assign(mustache_type); | ||
| 281 | bf.beard_type.Assign(beard_type); | ||
| 282 | bf.mustache_y.Assign(10); | ||
| 283 | } else { | ||
| 284 | bf.mustache_type.Assign(MustacheType::None); | ||
| 285 | bf.beard_type.Assign(BeardType::None); | ||
| 286 | bf.mustache_y.Assign(axis_y + 10); | ||
| 287 | } | ||
| 288 | |||
| 289 | const auto glasses_type_start{GetRandomValue<std::size_t>(100)}; | ||
| 290 | u8 glasses_type{}; | ||
| 291 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { | ||
| 292 | if (++glasses_type >= glasses_type_info.values_count) { | ||
| 293 | ASSERT(false); | ||
| 294 | break; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | bf.glasses_type.Assign(glasses_type); | ||
| 299 | bf.glasses_color.Assign(GlassesColorLookup[0]); | ||
| 300 | bf.glasses_scale.Assign(4); | ||
| 301 | bf.glasses_y.Assign(axis_y + 10); | ||
| 302 | |||
| 303 | bf.mole_type.Assign(0); | ||
| 304 | bf.mole_scale.Assign(4); | ||
| 305 | bf.mole_x.Assign(2); | ||
| 306 | bf.mole_y.Assign(20); | ||
| 307 | |||
| 308 | return {DefaultMiiName, bf, user_id}; | ||
| 309 | } | ||
| 310 | |||
| 311 | MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { | ||
| 312 | MiiStoreBitFields bf{}; | ||
| 313 | |||
| 314 | bf.font_region.Assign(info.font_region); | ||
| 315 | bf.favorite_color.Assign(info.favorite_color); | ||
| 316 | bf.gender.Assign(info.gender); | ||
| 317 | bf.height.Assign(info.height); | ||
| 318 | bf.build.Assign(info.weight); | ||
| 319 | bf.type.Assign(info.type); | ||
| 320 | bf.region_move.Assign(info.region); | ||
| 321 | bf.faceline_type.Assign(info.face_type); | ||
| 322 | bf.faceline_color.Assign(info.face_color); | ||
| 323 | bf.faceline_wrinkle.Assign(info.face_wrinkle); | ||
| 324 | bf.faceline_makeup.Assign(info.face_makeup); | ||
| 325 | bf.hair_type.Assign(info.hair_type); | ||
| 326 | bf.hair_color.Assign(HairColorLookup[info.hair_color]); | ||
| 327 | bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip)); | ||
| 328 | bf.eye_type.Assign(info.eye_type); | ||
| 329 | bf.eye_color.Assign(EyeColorLookup[info.eye_color]); | ||
| 330 | bf.eye_scale.Assign(info.eye_scale); | ||
| 331 | bf.eye_aspect.Assign(info.eye_aspect); | ||
| 332 | bf.eye_rotate.Assign(info.eye_rotate); | ||
| 333 | bf.eye_x.Assign(info.eye_x); | ||
| 334 | bf.eye_y.Assign(info.eye_y); | ||
| 335 | bf.eyebrow_type.Assign(info.eyebrow_type); | ||
| 336 | bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); | ||
| 337 | bf.eyebrow_scale.Assign(info.eyebrow_scale); | ||
| 338 | bf.eyebrow_aspect.Assign(info.eyebrow_aspect); | ||
| 339 | bf.eyebrow_rotate.Assign(info.eyebrow_rotate); | ||
| 340 | bf.eyebrow_x.Assign(info.eyebrow_x); | ||
| 341 | bf.eyebrow_y.Assign(info.eyebrow_y - 3); | ||
| 342 | bf.nose_type.Assign(info.nose_type); | ||
| 343 | bf.nose_scale.Assign(info.nose_scale); | ||
| 344 | bf.nose_y.Assign(info.nose_y); | ||
| 345 | bf.mouth_type.Assign(info.mouth_type); | ||
| 346 | bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); | ||
| 347 | bf.mouth_scale.Assign(info.mouth_scale); | ||
| 348 | bf.mouth_aspect.Assign(info.mouth_aspect); | ||
| 349 | bf.mouth_y.Assign(info.mouth_y); | ||
| 350 | bf.beard_color.Assign(HairColorLookup[info.beard_color]); | ||
| 351 | bf.beard_type.Assign(static_cast<BeardType>(info.beard_type)); | ||
| 352 | bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type)); | ||
| 353 | bf.mustache_scale.Assign(info.mustache_scale); | ||
| 354 | bf.mustache_y.Assign(info.mustache_y); | ||
| 355 | bf.glasses_type.Assign(info.glasses_type); | ||
| 356 | bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); | ||
| 357 | bf.glasses_scale.Assign(info.glasses_scale); | ||
| 358 | bf.glasses_y.Assign(info.glasses_y); | ||
| 359 | bf.mole_type.Assign(info.mole_type); | ||
| 360 | bf.mole_scale.Assign(info.mole_scale); | ||
| 361 | bf.mole_x.Assign(info.mole_x); | ||
| 362 | bf.mole_y.Assign(info.mole_y); | ||
| 363 | |||
| 364 | return {DefaultMiiName, bf, user_id}; | ||
| 365 | } | ||
| 366 | |||
| 367 | } // namespace | ||
| 368 | |||
| 369 | MiiStoreData::MiiStoreData() = default; | ||
| 370 | |||
| 371 | MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, | ||
| 372 | const Common::UUID& user_id) { | ||
| 373 | data.name = name; | ||
| 374 | data.uuid = Common::UUID::MakeRandomRFC4122V4(); | ||
| 375 | |||
| 376 | std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); | ||
| 377 | data_crc = GenerateCrc16(data.data.data(), sizeof(data)); | ||
| 378 | device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); | ||
| 379 | } | ||
| 380 | 22 | ||
| 381 | MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} | 23 | bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { |
| 382 | |||
| 383 | bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { | ||
| 384 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 24 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 385 | return false; | 25 | return false; |
| 386 | } | 26 | } |
| 387 | 27 | ||
| 388 | const bool result{current_update_counter != update_counter}; | 28 | const auto metadata_update_counter = metadata.update_counter; |
| 389 | 29 | metadata.update_counter = update_counter; | |
| 390 | current_update_counter = update_counter; | 30 | return metadata_update_counter != update_counter; |
| 391 | |||
| 392 | return result; | ||
| 393 | } | 31 | } |
| 394 | 32 | ||
| 395 | bool MiiManager::IsFullDatabase() const { | 33 | bool MiiManager::IsFullDatabase() const { |
| @@ -397,301 +35,138 @@ bool MiiManager::IsFullDatabase() const { | |||
| 397 | return false; | 35 | return false; |
| 398 | } | 36 | } |
| 399 | 37 | ||
| 400 | u32 MiiManager::GetCount(SourceFlag source_flag) const { | 38 | u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { |
| 401 | std::size_t count{}; | 39 | u32 mii_count{}; |
| 40 | if ((source_flag & SourceFlag::Default) != SourceFlag::None) { | ||
| 41 | mii_count += DefaultMiiCount; | ||
| 42 | } | ||
| 402 | if ((source_flag & SourceFlag::Database) != SourceFlag::None) { | 43 | if ((source_flag & SourceFlag::Database) != SourceFlag::None) { |
| 403 | // TODO(bunnei): We don't implement the Mii database, but when we do, update this | 44 | // TODO(bunnei): We don't implement the Mii database, but when we do, update this |
| 404 | count += 0; | ||
| 405 | } | 45 | } |
| 406 | if ((source_flag & SourceFlag::Default) != SourceFlag::None) { | 46 | return mii_count; |
| 407 | count += (DefaultMiiCount - BaseMiiCount); | ||
| 408 | } | ||
| 409 | return static_cast<u32>(count); | ||
| 410 | } | 47 | } |
| 411 | 48 | ||
| 412 | Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { | 49 | Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, |
| 50 | const CharInfo& char_info, SourceFlag source_flag) { | ||
| 413 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 51 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 414 | return ERROR_CANNOT_FIND_ENTRY; | 52 | return ResultNotFound; |
| 415 | } | 53 | } |
| 416 | 54 | ||
| 417 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry | 55 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry |
| 418 | return ERROR_CANNOT_FIND_ENTRY; | 56 | return ResultNotFound; |
| 419 | } | 57 | } |
| 420 | 58 | ||
| 421 | CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { | 59 | void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { |
| 422 | return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); | 60 | StoreData store_data{}; |
| 61 | store_data.BuildDefault(index); | ||
| 62 | out_char_info.SetFromStoreData(store_data); | ||
| 423 | } | 63 | } |
| 424 | 64 | ||
| 425 | CharInfo MiiManager::BuildDefault(std::size_t index) { | 65 | void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { |
| 426 | return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); | 66 | StoreData store_data{}; |
| 67 | store_data.BuildBase(gender); | ||
| 68 | out_char_info.SetFromStoreData(store_data); | ||
| 427 | } | 69 | } |
| 428 | 70 | ||
| 429 | CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { | 71 | void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { |
| 430 | Service::Mii::MiiManager manager; | 72 | StoreData store_data{}; |
| 431 | auto mii = manager.BuildDefault(0); | 73 | store_data.BuildRandom(age, gender, race); |
| 432 | 74 | out_char_info.SetFromStoreData(store_data); | |
| 433 | if (!ValidateV3Info(mii_v3)) { | ||
| 434 | return mii; | ||
| 435 | } | ||
| 436 | |||
| 437 | // TODO: We are ignoring a bunch of data from the mii_v3 | ||
| 438 | |||
| 439 | mii.gender = static_cast<u8>(mii_v3.mii_information.gender); | ||
| 440 | mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color); | ||
| 441 | mii.height = mii_v3.height; | ||
| 442 | mii.build = mii_v3.build; | ||
| 443 | |||
| 444 | // Copy name until string terminator | ||
| 445 | mii.name = {}; | ||
| 446 | for (std::size_t index = 0; index < mii.name.size() - 1; index++) { | ||
| 447 | mii.name[index] = mii_v3.mii_name[index]; | ||
| 448 | if (mii.name[index] == 0) { | ||
| 449 | break; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | mii.font_region = mii_v3.region_information.character_set; | ||
| 454 | |||
| 455 | mii.faceline_type = mii_v3.appearance_bits1.face_shape; | ||
| 456 | mii.faceline_color = mii_v3.appearance_bits1.skin_color; | ||
| 457 | mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles; | ||
| 458 | mii.faceline_make = mii_v3.appearance_bits2.makeup; | ||
| 459 | |||
| 460 | mii.hair_type = mii_v3.hair_style; | ||
| 461 | mii.hair_color = mii_v3.appearance_bits3.hair_color; | ||
| 462 | mii.hair_flip = mii_v3.appearance_bits3.flip_hair; | ||
| 463 | |||
| 464 | mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type); | ||
| 465 | mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color); | ||
| 466 | mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale); | ||
| 467 | mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch); | ||
| 468 | mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation); | ||
| 469 | mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing); | ||
| 470 | mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position); | ||
| 471 | |||
| 472 | mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style); | ||
| 473 | mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color); | ||
| 474 | mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale); | ||
| 475 | mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale); | ||
| 476 | mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation); | ||
| 477 | mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing); | ||
| 478 | mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position); | ||
| 479 | |||
| 480 | mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type); | ||
| 481 | mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale); | ||
| 482 | mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position); | ||
| 483 | |||
| 484 | mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type); | ||
| 485 | mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color); | ||
| 486 | mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale); | ||
| 487 | mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch); | ||
| 488 | mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position); | ||
| 489 | |||
| 490 | mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type); | ||
| 491 | mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale); | ||
| 492 | mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position); | ||
| 493 | |||
| 494 | mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type); | ||
| 495 | mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color); | ||
| 496 | |||
| 497 | mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type); | ||
| 498 | mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color); | ||
| 499 | mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale); | ||
| 500 | mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position); | ||
| 501 | |||
| 502 | mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled); | ||
| 503 | mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale); | ||
| 504 | mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position); | ||
| 505 | mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position); | ||
| 506 | |||
| 507 | // TODO: Validate mii data | ||
| 508 | |||
| 509 | return mii; | ||
| 510 | } | 75 | } |
| 511 | 76 | ||
| 512 | Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { | 77 | void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { |
| 513 | Service::Mii::MiiManager manager; | 78 | StoreData store_data{}; |
| 514 | Ver3StoreData mii_v3{}; | 79 | mii_v3.BuildToStoreData(store_data); |
| 515 | 80 | out_char_info.SetFromStoreData(store_data); | |
| 516 | // TODO: We are ignoring a bunch of data from the mii_v3 | 81 | } |
| 517 | |||
| 518 | mii_v3.version = 1; | ||
| 519 | mii_v3.mii_information.gender.Assign(mii.gender); | ||
| 520 | mii_v3.mii_information.favorite_color.Assign(mii.favorite_color); | ||
| 521 | mii_v3.height = mii.height; | ||
| 522 | mii_v3.build = mii.build; | ||
| 523 | 82 | ||
| 524 | // Copy name until string terminator | 83 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, |
| 525 | mii_v3.mii_name = {}; | 84 | std::span<CharInfoElement> out_elements, u32& out_count, |
| 526 | for (std::size_t index = 0; index < mii.name.size() - 1; index++) { | 85 | SourceFlag source_flag) { |
| 527 | mii_v3.mii_name[index] = mii.name[index]; | 86 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 528 | if (mii_v3.mii_name[index] == 0) { | 87 | return BuildDefault(out_elements, out_count, source_flag); |
| 529 | break; | ||
| 530 | } | ||
| 531 | } | 88 | } |
| 532 | 89 | ||
| 533 | mii_v3.region_information.character_set.Assign(mii.font_region); | 90 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry |
| 534 | 91 | ||
| 535 | mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type); | 92 | // Include default Mii at the end of the list |
| 536 | mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle); | 93 | return BuildDefault(out_elements, out_count, source_flag); |
| 537 | mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make); | 94 | } |
| 538 | 95 | ||
| 539 | mii_v3.hair_style = mii.hair_type; | 96 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, |
| 540 | mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip); | 97 | u32& out_count, SourceFlag source_flag) { |
| 98 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||
| 99 | return BuildDefault(out_char_info, out_count, source_flag); | ||
| 100 | } | ||
| 541 | 101 | ||
| 542 | mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type); | 102 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry |
| 543 | mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale); | ||
| 544 | mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect); | ||
| 545 | mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate); | ||
| 546 | mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x); | ||
| 547 | mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y); | ||
| 548 | 103 | ||
| 549 | mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type); | 104 | // Include default Mii at the end of the list |
| 550 | mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale); | 105 | return BuildDefault(out_char_info, out_count, source_flag); |
| 551 | mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect); | 106 | } |
| 552 | mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate); | ||
| 553 | mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x); | ||
| 554 | mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y); | ||
| 555 | 107 | ||
| 556 | mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type); | 108 | Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, |
| 557 | mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale); | 109 | SourceFlag source_flag) { |
| 558 | mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y); | 110 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { |
| 111 | return ResultSuccess; | ||
| 112 | } | ||
| 559 | 113 | ||
| 560 | mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type); | 114 | StoreData store_data{}; |
| 561 | mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale); | ||
| 562 | mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect); | ||
| 563 | mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y); | ||
| 564 | 115 | ||
| 565 | mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type); | 116 | for (std::size_t index = 0; index < DefaultMiiCount; ++index) { |
| 566 | mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale); | 117 | if (out_elements.size() <= static_cast<std::size_t>(out_count)) { |
| 567 | mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y); | 118 | return ResultInvalidArgumentSize; |
| 119 | } | ||
| 568 | 120 | ||
| 569 | mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type); | 121 | store_data.BuildDefault(static_cast<u32>(index)); |
| 570 | 122 | ||
| 571 | mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale); | 123 | out_elements[out_count].source = Source::Default; |
| 572 | mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y); | 124 | out_elements[out_count].char_info.SetFromStoreData(store_data); |
| 125 | out_count++; | ||
| 126 | } | ||
| 573 | 127 | ||
| 574 | mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type); | 128 | return ResultSuccess; |
| 575 | mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale); | 129 | } |
| 576 | mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x); | ||
| 577 | mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y); | ||
| 578 | 130 | ||
| 579 | // These types are converted to V3 from a table | 131 | Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, |
| 580 | mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]); | 132 | SourceFlag source_flag) { |
| 581 | mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]); | 133 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { |
| 582 | mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]); | 134 | return ResultSuccess; |
| 583 | mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]); | 135 | } |
| 584 | mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]); | ||
| 585 | mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]); | ||
| 586 | mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]); | ||
| 587 | mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]); | ||
| 588 | 136 | ||
| 589 | mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16)); | 137 | StoreData store_data{}; |
| 590 | 138 | ||
| 591 | // TODO: Validate mii_v3 data | 139 | for (std::size_t index = 0; index < DefaultMiiCount; ++index) { |
| 140 | if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { | ||
| 141 | return ResultInvalidArgumentSize; | ||
| 142 | } | ||
| 592 | 143 | ||
| 593 | return mii_v3; | 144 | store_data.BuildDefault(static_cast<u32>(index)); |
| 594 | } | ||
| 595 | 145 | ||
| 596 | NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const { | 146 | out_char_info[out_count].SetFromStoreData(store_data); |
| 597 | return { | 147 | out_count++; |
| 598 | .faceline_color = static_cast<u8>(mii.faceline_color & 0xf), | 148 | } |
| 599 | .hair_color = static_cast<u8>(mii.hair_color & 0x7f), | ||
| 600 | .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f), | ||
| 601 | .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f), | ||
| 602 | .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f), | ||
| 603 | .beard_color = static_cast<u8>(mii.beard_color & 0x7f), | ||
| 604 | .glass_color = static_cast<u8>(mii.glasses_color & 0x7f), | ||
| 605 | .glass_type = static_cast<u8>(mii.glasses_type & 0x1f), | ||
| 606 | }; | ||
| 607 | } | ||
| 608 | 149 | ||
| 609 | bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { | 150 | return ResultSuccess; |
| 610 | bool is_valid = mii_v3.version == 0 || mii_v3.version == 3; | ||
| 611 | |||
| 612 | is_valid = is_valid && (mii_v3.mii_name[0] != 0); | ||
| 613 | |||
| 614 | is_valid = is_valid && (mii_v3.mii_information.birth_month < 13); | ||
| 615 | is_valid = is_valid && (mii_v3.mii_information.birth_day < 32); | ||
| 616 | is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12); | ||
| 617 | is_valid = is_valid && (mii_v3.height < 128); | ||
| 618 | is_valid = is_valid && (mii_v3.build < 128); | ||
| 619 | |||
| 620 | is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12); | ||
| 621 | is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7); | ||
| 622 | is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12); | ||
| 623 | is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12); | ||
| 624 | |||
| 625 | is_valid = is_valid && (mii_v3.hair_style < 132); | ||
| 626 | is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8); | ||
| 627 | |||
| 628 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60); | ||
| 629 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6); | ||
| 630 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8); | ||
| 631 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7); | ||
| 632 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8); | ||
| 633 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13); | ||
| 634 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19); | ||
| 635 | |||
| 636 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25); | ||
| 637 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8); | ||
| 638 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9); | ||
| 639 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7); | ||
| 640 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12); | ||
| 641 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12); | ||
| 642 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19); | ||
| 643 | |||
| 644 | is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18); | ||
| 645 | is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9); | ||
| 646 | is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19); | ||
| 647 | |||
| 648 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36); | ||
| 649 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5); | ||
| 650 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9); | ||
| 651 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7); | ||
| 652 | is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19); | ||
| 653 | |||
| 654 | is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6); | ||
| 655 | is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7); | ||
| 656 | is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17); | ||
| 657 | |||
| 658 | is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6); | ||
| 659 | is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8); | ||
| 660 | |||
| 661 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9); | ||
| 662 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6); | ||
| 663 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8); | ||
| 664 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21); | ||
| 665 | |||
| 666 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2); | ||
| 667 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9); | ||
| 668 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17); | ||
| 669 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31); | ||
| 670 | |||
| 671 | return is_valid; | ||
| 672 | } | 151 | } |
| 673 | 152 | ||
| 674 | std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) { | 153 | Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, |
| 675 | std::vector<MiiInfoElement> result; | 154 | s32& out_index) { |
| 676 | 155 | ||
| 677 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | 156 | if (char_info.Verify() != ValidationResult::NoErrors) { |
| 678 | return result; | 157 | return ResultInvalidCharInfo; |
| 679 | } | 158 | } |
| 680 | 159 | ||
| 681 | for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { | ||
| 682 | result.emplace_back(BuildDefault(index), Source::Default); | ||
| 683 | } | ||
| 684 | |||
| 685 | return result; | ||
| 686 | } | ||
| 687 | |||
| 688 | Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { | ||
| 689 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; | 160 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; |
| 690 | 161 | ||
| 691 | index = INVALID_INDEX; | 162 | out_index = INVALID_INDEX; |
| 692 | 163 | ||
| 693 | // TODO(bunnei): We don't implement the Mii database, so we can't have an index | 164 | // TODO(bunnei): We don't implement the Mii database, so we can't have an index |
| 694 | return ERROR_CANNOT_FIND_ENTRY; | 165 | return ResultNotFound; |
| 166 | } | ||
| 167 | |||
| 168 | void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) { | ||
| 169 | metadata.interface_version = version; | ||
| 695 | } | 170 | } |
| 696 | 171 | ||
| 697 | } // namespace Service::Mii | 172 | } // namespace Service::Mii |
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 45c2be3c8..a2e7a6d73 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h | |||
| @@ -6,7 +6,10 @@ | |||
| 6 | #include <vector> | 6 | #include <vector> |
| 7 | 7 | ||
| 8 | #include "core/hle/result.h" | 8 | #include "core/hle/result.h" |
| 9 | #include "core/hle/service/mii/types.h" | 9 | #include "core/hle/service/mii/mii_types.h" |
| 10 | #include "core/hle/service/mii/types/char_info.h" | ||
| 11 | #include "core/hle/service/mii/types/store_data.h" | ||
| 12 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 10 | 13 | ||
| 11 | namespace Service::Mii { | 14 | namespace Service::Mii { |
| 12 | 15 | ||
| @@ -16,25 +19,30 @@ class MiiManager { | |||
| 16 | public: | 19 | public: |
| 17 | MiiManager(); | 20 | MiiManager(); |
| 18 | 21 | ||
| 19 | bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); | 22 | bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; |
| 20 | bool IsFullDatabase() const; | ||
| 21 | u32 GetCount(SourceFlag source_flag) const; | ||
| 22 | Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag); | ||
| 23 | CharInfo BuildRandom(Age age, Gender gender, Race race); | ||
| 24 | CharInfo BuildDefault(std::size_t index); | ||
| 25 | CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; | ||
| 26 | bool ValidateV3Info(const Ver3StoreData& mii_v3) const; | ||
| 27 | std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag); | ||
| 28 | Result GetIndex(const CharInfo& info, u32& index); | ||
| 29 | |||
| 30 | // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData | ||
| 31 | Ver3StoreData BuildFromStoreData(const CharInfo& mii) const; | ||
| 32 | 23 | ||
| 33 | // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData | 24 | bool IsFullDatabase() const; |
| 34 | NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; | 25 | u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; |
| 26 | Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, | ||
| 27 | const CharInfo& char_info, SourceFlag source_flag); | ||
| 28 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, | ||
| 29 | u32& out_count, SourceFlag source_flag); | ||
| 30 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, | ||
| 31 | u32& out_count, SourceFlag source_flag); | ||
| 32 | void BuildDefault(CharInfo& out_char_info, u32 index) const; | ||
| 33 | void BuildBase(CharInfo& out_char_info, Gender gender) const; | ||
| 34 | void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; | ||
| 35 | void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; | ||
| 36 | std::vector<CharInfoElement> GetDefault(SourceFlag source_flag); | ||
| 37 | Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, | ||
| 38 | s32& out_index); | ||
| 39 | void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version); | ||
| 35 | 40 | ||
| 36 | private: | 41 | private: |
| 37 | const Common::UUID user_id{}; | 42 | Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, |
| 43 | SourceFlag source_flag); | ||
| 44 | Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag); | ||
| 45 | |||
| 38 | u64 update_counter{}; | 46 | u64 update_counter{}; |
| 39 | }; | 47 | }; |
| 40 | 48 | ||
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h new file mode 100644 index 000000000..021cb76da --- /dev/null +++ b/src/core/hle/service/mii/mii_result.h | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1}; | ||
| 11 | constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2}; | ||
| 12 | constexpr Result ResultNotUpdated{ErrorModule::Mii, 3}; | ||
| 13 | constexpr Result ResultNotFound{ErrorModule::Mii, 4}; | ||
| 14 | constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; | ||
| 15 | constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; | ||
| 16 | constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; | ||
| 17 | constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; | ||
| 18 | constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; | ||
| 19 | |||
| 20 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h new file mode 100644 index 000000000..611ff4f81 --- /dev/null +++ b/src/core/hle/service/mii/mii_types.h | |||
| @@ -0,0 +1,691 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <type_traits> | ||
| 8 | |||
| 9 | #include "common/bit_field.h" | ||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/uuid.h" | ||
| 13 | |||
| 14 | namespace Service::Mii { | ||
| 15 | |||
| 16 | constexpr u8 MaxHeight = 127; | ||
| 17 | constexpr u8 MaxBuild = 127; | ||
| 18 | constexpr u8 MaxType = 1; | ||
| 19 | constexpr u8 MaxRegionMove = 3; | ||
| 20 | constexpr u8 MaxEyeScale = 7; | ||
| 21 | constexpr u8 MaxEyeAspect = 6; | ||
| 22 | constexpr u8 MaxEyeRotate = 7; | ||
| 23 | constexpr u8 MaxEyeX = 12; | ||
| 24 | constexpr u8 MaxEyeY = 18; | ||
| 25 | constexpr u8 MaxEyebrowScale = 8; | ||
| 26 | constexpr u8 MaxEyebrowAspect = 6; | ||
| 27 | constexpr u8 MaxEyebrowRotate = 11; | ||
| 28 | constexpr u8 MaxEyebrowX = 12; | ||
| 29 | constexpr u8 MaxEyebrowY = 18; | ||
| 30 | constexpr u8 MaxNoseScale = 8; | ||
| 31 | constexpr u8 MaxNoseY = 18; | ||
| 32 | constexpr u8 MaxMouthScale = 8; | ||
| 33 | constexpr u8 MaxMoutAspect = 6; | ||
| 34 | constexpr u8 MaxMouthY = 18; | ||
| 35 | constexpr u8 MaxMustacheScale = 8; | ||
| 36 | constexpr u8 MasMustacheY = 16; | ||
| 37 | constexpr u8 MaxGlassScale = 7; | ||
| 38 | constexpr u8 MaxGlassY = 20; | ||
| 39 | constexpr u8 MaxMoleScale = 8; | ||
| 40 | constexpr u8 MaxMoleX = 16; | ||
| 41 | constexpr u8 MaxMoleY = 30; | ||
| 42 | constexpr u8 MaxVer3CommonColor = 7; | ||
| 43 | constexpr u8 MaxVer3GlassType = 8; | ||
| 44 | |||
| 45 | enum class Age : u8 { | ||
| 46 | Young, | ||
| 47 | Normal, | ||
| 48 | Old, | ||
| 49 | All, // Default | ||
| 50 | |||
| 51 | Max = All, | ||
| 52 | }; | ||
| 53 | |||
| 54 | enum class Gender : u8 { | ||
| 55 | Male, | ||
| 56 | Female, | ||
| 57 | All, // Default | ||
| 58 | |||
| 59 | Max = Female, | ||
| 60 | }; | ||
| 61 | |||
| 62 | enum class Race : u8 { | ||
| 63 | Black, | ||
| 64 | White, | ||
| 65 | Asian, | ||
| 66 | All, // Default | ||
| 67 | |||
| 68 | Max = All, | ||
| 69 | }; | ||
| 70 | |||
| 71 | enum class HairType : u8 { | ||
| 72 | NormalLong, // Default | ||
| 73 | NormalShort, | ||
| 74 | NormalMedium, | ||
| 75 | NormalExtraLong, | ||
| 76 | NormalLongBottom, | ||
| 77 | NormalTwoPeaks, | ||
| 78 | PartingLong, | ||
| 79 | FrontLock, | ||
| 80 | PartingShort, | ||
| 81 | PartingExtraLongCurved, | ||
| 82 | PartingExtraLong, | ||
| 83 | PartingMiddleLong, | ||
| 84 | PartingSquared, | ||
| 85 | PartingLongBottom, | ||
| 86 | PeaksTop, | ||
| 87 | PeaksSquared, | ||
| 88 | PartingPeaks, | ||
| 89 | PeaksLongBottom, | ||
| 90 | Peaks, | ||
| 91 | PeaksRounded, | ||
| 92 | PeaksSide, | ||
| 93 | PeaksMedium, | ||
| 94 | PeaksLong, | ||
| 95 | PeaksRoundedLong, | ||
| 96 | PartingFrontPeaks, | ||
| 97 | PartingLongFront, | ||
| 98 | PartingLongRounded, | ||
| 99 | PartingFrontPeaksLong, | ||
| 100 | PartingExtraLongRounded, | ||
| 101 | LongRounded, | ||
| 102 | NormalUnknown1, | ||
| 103 | NormalUnknown2, | ||
| 104 | NormalUnknown3, | ||
| 105 | NormalUnknown4, | ||
| 106 | NormalUnknown5, | ||
| 107 | NormalUnknown6, | ||
| 108 | DreadLocks, | ||
| 109 | PlatedMats, | ||
| 110 | Caps, | ||
| 111 | Afro, | ||
| 112 | PlatedMatsLong, | ||
| 113 | Beanie, | ||
| 114 | Short, | ||
| 115 | ShortTopLongSide, | ||
| 116 | ShortUnknown1, | ||
| 117 | ShortUnknown2, | ||
| 118 | MilitaryParting, | ||
| 119 | Military, | ||
| 120 | ShortUnknown3, | ||
| 121 | ShortUnknown4, | ||
| 122 | ShortUnknown5, | ||
| 123 | ShortUnknown6, | ||
| 124 | NoneTop, | ||
| 125 | None, | ||
| 126 | LongUnknown1, | ||
| 127 | LongUnknown2, | ||
| 128 | LongUnknown3, | ||
| 129 | LongUnknown4, | ||
| 130 | LongUnknown5, | ||
| 131 | LongUnknown6, | ||
| 132 | LongUnknown7, | ||
| 133 | LongUnknown8, | ||
| 134 | LongUnknown9, | ||
| 135 | LongUnknown10, | ||
| 136 | LongUnknown11, | ||
| 137 | LongUnknown12, | ||
| 138 | LongUnknown13, | ||
| 139 | LongUnknown14, | ||
| 140 | LongUnknown15, | ||
| 141 | LongUnknown16, | ||
| 142 | LongUnknown17, | ||
| 143 | LongUnknown18, | ||
| 144 | LongUnknown19, | ||
| 145 | LongUnknown20, | ||
| 146 | LongUnknown21, | ||
| 147 | LongUnknown22, | ||
| 148 | LongUnknown23, | ||
| 149 | LongUnknown24, | ||
| 150 | LongUnknown25, | ||
| 151 | LongUnknown26, | ||
| 152 | LongUnknown27, | ||
| 153 | LongUnknown28, | ||
| 154 | LongUnknown29, | ||
| 155 | LongUnknown30, | ||
| 156 | LongUnknown31, | ||
| 157 | LongUnknown32, | ||
| 158 | LongUnknown33, | ||
| 159 | LongUnknown34, | ||
| 160 | LongUnknown35, | ||
| 161 | LongUnknown36, | ||
| 162 | LongUnknown37, | ||
| 163 | LongUnknown38, | ||
| 164 | LongUnknown39, | ||
| 165 | LongUnknown40, | ||
| 166 | LongUnknown41, | ||
| 167 | LongUnknown42, | ||
| 168 | LongUnknown43, | ||
| 169 | LongUnknown44, | ||
| 170 | LongUnknown45, | ||
| 171 | LongUnknown46, | ||
| 172 | LongUnknown47, | ||
| 173 | LongUnknown48, | ||
| 174 | LongUnknown49, | ||
| 175 | LongUnknown50, | ||
| 176 | LongUnknown51, | ||
| 177 | LongUnknown52, | ||
| 178 | LongUnknown53, | ||
| 179 | LongUnknown54, | ||
| 180 | LongUnknown55, | ||
| 181 | LongUnknown56, | ||
| 182 | LongUnknown57, | ||
| 183 | LongUnknown58, | ||
| 184 | LongUnknown59, | ||
| 185 | LongUnknown60, | ||
| 186 | LongUnknown61, | ||
| 187 | LongUnknown62, | ||
| 188 | LongUnknown63, | ||
| 189 | LongUnknown64, | ||
| 190 | LongUnknown65, | ||
| 191 | LongUnknown66, | ||
| 192 | TwoMediumFrontStrandsOneLongBackPonyTail, | ||
| 193 | TwoFrontStrandsLongBackPonyTail, | ||
| 194 | PartingFrontTwoLongBackPonyTails, | ||
| 195 | TwoFrontStrandsOneLongBackPonyTail, | ||
| 196 | LongBackPonyTail, | ||
| 197 | LongFrontTwoLongBackPonyTails, | ||
| 198 | StrandsTwoShortSidedPonyTails, | ||
| 199 | TwoMediumSidedPonyTails, | ||
| 200 | ShortFrontTwoBackPonyTails, | ||
| 201 | TwoShortSidedPonyTails, | ||
| 202 | TwoLongSidedPonyTails, | ||
| 203 | LongFrontTwoBackPonyTails, | ||
| 204 | |||
| 205 | Max = LongFrontTwoBackPonyTails, | ||
| 206 | }; | ||
| 207 | |||
| 208 | enum class MoleType : u8 { | ||
| 209 | None, // Default | ||
| 210 | OneDot, | ||
| 211 | |||
| 212 | Max = OneDot, | ||
| 213 | }; | ||
| 214 | |||
| 215 | enum class HairFlip : u8 { | ||
| 216 | Left, // Default | ||
| 217 | Right, | ||
| 218 | |||
| 219 | Max = Right, | ||
| 220 | }; | ||
| 221 | |||
| 222 | enum class CommonColor : u8 { | ||
| 223 | // For simplicity common colors aren't listed | ||
| 224 | Max = 99, | ||
| 225 | Count = 100, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class FavoriteColor : u8 { | ||
| 229 | Red, // Default | ||
| 230 | Orange, | ||
| 231 | Yellow, | ||
| 232 | LimeGreen, | ||
| 233 | Green, | ||
| 234 | Blue, | ||
| 235 | LightBlue, | ||
| 236 | Pink, | ||
| 237 | Purple, | ||
| 238 | Brown, | ||
| 239 | White, | ||
| 240 | Black, | ||
| 241 | |||
| 242 | Max = Black, | ||
| 243 | }; | ||
| 244 | |||
| 245 | enum class EyeType : u8 { | ||
| 246 | Normal, // Default | ||
| 247 | NormalLash, | ||
| 248 | WhiteLash, | ||
| 249 | WhiteNoBottom, | ||
| 250 | OvalAngledWhite, | ||
| 251 | AngryWhite, | ||
| 252 | DotLashType1, | ||
| 253 | Line, | ||
| 254 | DotLine, | ||
| 255 | OvalWhite, | ||
| 256 | RoundedWhite, | ||
| 257 | NormalShadow, | ||
| 258 | CircleWhite, | ||
| 259 | Circle, | ||
| 260 | CircleWhiteStroke, | ||
| 261 | NormalOvalNoBottom, | ||
| 262 | NormalOvalLarge, | ||
| 263 | NormalRoundedNoBottom, | ||
| 264 | SmallLash, | ||
| 265 | Small, | ||
| 266 | TwoSmall, | ||
| 267 | NormalLongLash, | ||
| 268 | WhiteTwoLashes, | ||
| 269 | WhiteThreeLashes, | ||
| 270 | DotAngry, | ||
| 271 | DotAngled, | ||
| 272 | Oval, | ||
| 273 | SmallWhite, | ||
| 274 | WhiteAngledNoBottom, | ||
| 275 | WhiteAngledNoLeft, | ||
| 276 | SmallWhiteTwoLashes, | ||
| 277 | LeafWhiteLash, | ||
| 278 | WhiteLargeNoBottom, | ||
| 279 | Dot, | ||
| 280 | DotLashType2, | ||
| 281 | DotThreeLashes, | ||
| 282 | WhiteOvalTop, | ||
| 283 | WhiteOvalBottom, | ||
| 284 | WhiteOvalBottomFlat, | ||
| 285 | WhiteOvalTwoLashes, | ||
| 286 | WhiteOvalThreeLashes, | ||
| 287 | WhiteOvalNoBottomTwoLashes, | ||
| 288 | DotWhite, | ||
| 289 | WhiteOvalTopFlat, | ||
| 290 | WhiteThinLeaf, | ||
| 291 | StarThreeLashes, | ||
| 292 | LineTwoLashes, | ||
| 293 | CrowsFeet, | ||
| 294 | WhiteNoBottomFlat, | ||
| 295 | WhiteNoBottomRounded, | ||
| 296 | WhiteSmallBottomLine, | ||
| 297 | WhiteNoBottomLash, | ||
| 298 | WhiteNoPartialBottomLash, | ||
| 299 | WhiteOvalBottomLine, | ||
| 300 | WhiteNoBottomLashTopLine, | ||
| 301 | WhiteNoPartialBottomTwoLashes, | ||
| 302 | NormalTopLine, | ||
| 303 | WhiteOvalLash, | ||
| 304 | RoundTired, | ||
| 305 | WhiteLarge, | ||
| 306 | |||
| 307 | Max = WhiteLarge, | ||
| 308 | }; | ||
| 309 | |||
| 310 | enum class MouthType : u8 { | ||
| 311 | Neutral, // Default | ||
| 312 | NeutralLips, | ||
| 313 | Smile, | ||
| 314 | SmileStroke, | ||
| 315 | SmileTeeth, | ||
| 316 | LipsSmall, | ||
| 317 | LipsLarge, | ||
| 318 | Wave, | ||
| 319 | WaveAngrySmall, | ||
| 320 | NeutralStrokeLarge, | ||
| 321 | TeethSurprised, | ||
| 322 | LipsExtraLarge, | ||
| 323 | LipsUp, | ||
| 324 | NeutralDown, | ||
| 325 | Surprised, | ||
| 326 | TeethMiddle, | ||
| 327 | NeutralStroke, | ||
| 328 | LipsExtraSmall, | ||
| 329 | Malicious, | ||
| 330 | LipsDual, | ||
| 331 | NeutralComma, | ||
| 332 | NeutralUp, | ||
| 333 | TeethLarge, | ||
| 334 | WaveAngry, | ||
| 335 | LipsSexy, | ||
| 336 | SmileInverted, | ||
| 337 | LipsSexyOutline, | ||
| 338 | SmileRounded, | ||
| 339 | LipsTeeth, | ||
| 340 | NeutralOpen, | ||
| 341 | TeethRounded, | ||
| 342 | WaveAngrySmallInverted, | ||
| 343 | NeutralCommaInverted, | ||
| 344 | TeethFull, | ||
| 345 | SmileDownLine, | ||
| 346 | Kiss, | ||
| 347 | |||
| 348 | Max = Kiss, | ||
| 349 | }; | ||
| 350 | |||
| 351 | enum class FontRegion : u8 { | ||
| 352 | Standard, // Default | ||
| 353 | China, | ||
| 354 | Korea, | ||
| 355 | Taiwan, | ||
| 356 | |||
| 357 | Max = Taiwan, | ||
| 358 | }; | ||
| 359 | |||
| 360 | enum class FacelineType : u8 { | ||
| 361 | Sharp, // Default | ||
| 362 | Rounded, | ||
| 363 | SharpRounded, | ||
| 364 | SharpRoundedSmall, | ||
| 365 | Large, | ||
| 366 | LargeRounded, | ||
| 367 | SharpSmall, | ||
| 368 | Flat, | ||
| 369 | Bump, | ||
| 370 | Angular, | ||
| 371 | FlatRounded, | ||
| 372 | AngularSmall, | ||
| 373 | |||
| 374 | Max = AngularSmall, | ||
| 375 | }; | ||
| 376 | |||
| 377 | enum class FacelineColor : u8 { | ||
| 378 | Beige, // Default | ||
| 379 | WarmBeige, | ||
| 380 | Natural, | ||
| 381 | Honey, | ||
| 382 | Chestnut, | ||
| 383 | Porcelain, | ||
| 384 | Ivory, | ||
| 385 | WarmIvory, | ||
| 386 | Almond, | ||
| 387 | Espresso, | ||
| 388 | |||
| 389 | Max = Espresso, | ||
| 390 | Count = Max + 1, | ||
| 391 | }; | ||
| 392 | |||
| 393 | enum class FacelineWrinkle : u8 { | ||
| 394 | None, // Default | ||
| 395 | TearTroughs, | ||
| 396 | FacialPain, | ||
| 397 | Cheeks, | ||
| 398 | Folds, | ||
| 399 | UnderTheEyes, | ||
| 400 | SplitChin, | ||
| 401 | Chin, | ||
| 402 | BrowDroop, | ||
| 403 | MouthFrown, | ||
| 404 | CrowsFeet, | ||
| 405 | FoldsCrowsFrown, | ||
| 406 | |||
| 407 | Max = FoldsCrowsFrown, | ||
| 408 | }; | ||
| 409 | |||
| 410 | enum class FacelineMake : u8 { | ||
| 411 | None, // Default | ||
| 412 | CheekPorcelain, | ||
| 413 | CheekNatural, | ||
| 414 | EyeShadowBlue, | ||
| 415 | CheekBlushPorcelain, | ||
| 416 | CheekBlushNatural, | ||
| 417 | CheekPorcelainEyeShadowBlue, | ||
| 418 | CheekPorcelainEyeShadowNatural, | ||
| 419 | CheekBlushPorcelainEyeShadowEspresso, | ||
| 420 | Freckles, | ||
| 421 | LionsManeBeard, | ||
| 422 | StubbleBeard, | ||
| 423 | |||
| 424 | Max = StubbleBeard, | ||
| 425 | }; | ||
| 426 | |||
| 427 | enum class EyebrowType : u8 { | ||
| 428 | FlatAngledLarge, // Default | ||
| 429 | LowArchRoundedThin, | ||
| 430 | SoftAngledLarge, | ||
| 431 | MediumArchRoundedThin, | ||
| 432 | RoundedMedium, | ||
| 433 | LowArchMedium, | ||
| 434 | RoundedThin, | ||
| 435 | UpThin, | ||
| 436 | MediumArchRoundedMedium, | ||
| 437 | RoundedLarge, | ||
| 438 | UpLarge, | ||
| 439 | FlatAngledLargeInverted, | ||
| 440 | MediumArchFlat, | ||
| 441 | AngledThin, | ||
| 442 | HorizontalLarge, | ||
| 443 | HighArchFlat, | ||
| 444 | Flat, | ||
| 445 | MediumArchLarge, | ||
| 446 | LowArchThin, | ||
| 447 | RoundedThinInverted, | ||
| 448 | HighArchLarge, | ||
| 449 | Hairy, | ||
| 450 | Dotted, | ||
| 451 | None, | ||
| 452 | |||
| 453 | Max = None, | ||
| 454 | }; | ||
| 455 | |||
| 456 | enum class NoseType : u8 { | ||
| 457 | Normal, // Default | ||
| 458 | Rounded, | ||
| 459 | Dot, | ||
| 460 | Arrow, | ||
| 461 | Roman, | ||
| 462 | Triangle, | ||
| 463 | Button, | ||
| 464 | RoundedInverted, | ||
| 465 | Potato, | ||
| 466 | Grecian, | ||
| 467 | Snub, | ||
| 468 | Aquiline, | ||
| 469 | ArrowLeft, | ||
| 470 | RoundedLarge, | ||
| 471 | Hooked, | ||
| 472 | Fat, | ||
| 473 | Droopy, | ||
| 474 | ArrowLarge, | ||
| 475 | |||
| 476 | Max = ArrowLarge, | ||
| 477 | }; | ||
| 478 | |||
| 479 | enum class BeardType : u8 { | ||
| 480 | None, | ||
| 481 | Goatee, | ||
| 482 | GoateeLong, | ||
| 483 | LionsManeLong, | ||
| 484 | LionsMane, | ||
| 485 | Full, | ||
| 486 | |||
| 487 | Min = Goatee, | ||
| 488 | Max = Full, | ||
| 489 | }; | ||
| 490 | |||
| 491 | enum class MustacheType : u8 { | ||
| 492 | None, | ||
| 493 | Walrus, | ||
| 494 | Pencil, | ||
| 495 | Horseshoe, | ||
| 496 | Normal, | ||
| 497 | Toothbrush, | ||
| 498 | |||
| 499 | Min = Walrus, | ||
| 500 | Max = Toothbrush, | ||
| 501 | }; | ||
| 502 | |||
| 503 | enum class GlassType : u8 { | ||
| 504 | None, | ||
| 505 | Oval, | ||
| 506 | Wayfarer, | ||
| 507 | Rectangle, | ||
| 508 | TopRimless, | ||
| 509 | Rounded, | ||
| 510 | Oversized, | ||
| 511 | CatEye, | ||
| 512 | Square, | ||
| 513 | BottomRimless, | ||
| 514 | SemiOpaqueRounded, | ||
| 515 | SemiOpaqueCatEye, | ||
| 516 | SemiOpaqueOval, | ||
| 517 | SemiOpaqueRectangle, | ||
| 518 | SemiOpaqueAviator, | ||
| 519 | OpaqueRounded, | ||
| 520 | OpaqueCatEye, | ||
| 521 | OpaqueOval, | ||
| 522 | OpaqueRectangle, | ||
| 523 | OpaqueAviator, | ||
| 524 | |||
| 525 | Max = OpaqueAviator, | ||
| 526 | Count = Max + 1, | ||
| 527 | }; | ||
| 528 | |||
| 529 | enum class BeardAndMustacheFlag : u32 { | ||
| 530 | Beard = 1, | ||
| 531 | Mustache, | ||
| 532 | All = Beard | Mustache, | ||
| 533 | }; | ||
| 534 | DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); | ||
| 535 | |||
| 536 | enum class Source : u32 { | ||
| 537 | Database = 0, | ||
| 538 | Default = 1, | ||
| 539 | Account = 2, | ||
| 540 | Friend = 3, | ||
| 541 | }; | ||
| 542 | |||
| 543 | enum class SourceFlag : u32 { | ||
| 544 | None = 0, | ||
| 545 | Database = 1 << 0, | ||
| 546 | Default = 1 << 1, | ||
| 547 | }; | ||
| 548 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | ||
| 549 | |||
| 550 | enum class ValidationResult : u32 { | ||
| 551 | NoErrors = 0x0, | ||
| 552 | InvalidBeardColor = 0x1, | ||
| 553 | InvalidBeardType = 0x2, | ||
| 554 | InvalidBuild = 0x3, | ||
| 555 | InvalidEyeAspect = 0x4, | ||
| 556 | InvalidEyeColor = 0x5, | ||
| 557 | InvalidEyeRotate = 0x6, | ||
| 558 | InvalidEyeScale = 0x7, | ||
| 559 | InvalidEyeType = 0x8, | ||
| 560 | InvalidEyeX = 0x9, | ||
| 561 | InvalidEyeY = 0xa, | ||
| 562 | InvalidEyebrowAspect = 0xb, | ||
| 563 | InvalidEyebrowColor = 0xc, | ||
| 564 | InvalidEyebrowRotate = 0xd, | ||
| 565 | InvalidEyebrowScale = 0xe, | ||
| 566 | InvalidEyebrowType = 0xf, | ||
| 567 | InvalidEyebrowX = 0x10, | ||
| 568 | InvalidEyebrowY = 0x11, | ||
| 569 | InvalidFacelineColor = 0x12, | ||
| 570 | InvalidFacelineMake = 0x13, | ||
| 571 | InvalidFacelineWrinkle = 0x14, | ||
| 572 | InvalidFacelineType = 0x15, | ||
| 573 | InvalidColor = 0x16, | ||
| 574 | InvalidFont = 0x17, | ||
| 575 | InvalidGender = 0x18, | ||
| 576 | InvalidGlassColor = 0x19, | ||
| 577 | InvalidGlassScale = 0x1a, | ||
| 578 | InvalidGlassType = 0x1b, | ||
| 579 | InvalidGlassY = 0x1c, | ||
| 580 | InvalidHairColor = 0x1d, | ||
| 581 | InvalidHairFlip = 0x1e, | ||
| 582 | InvalidHairType = 0x1f, | ||
| 583 | InvalidHeight = 0x20, | ||
| 584 | InvalidMoleScale = 0x21, | ||
| 585 | InvalidMoleType = 0x22, | ||
| 586 | InvalidMoleX = 0x23, | ||
| 587 | InvalidMoleY = 0x24, | ||
| 588 | InvalidMouthAspect = 0x25, | ||
| 589 | InvalidMouthColor = 0x26, | ||
| 590 | InvalidMouthScale = 0x27, | ||
| 591 | InvalidMouthType = 0x28, | ||
| 592 | InvalidMouthY = 0x29, | ||
| 593 | InvalidMustacheScale = 0x2a, | ||
| 594 | InvalidMustacheType = 0x2b, | ||
| 595 | InvalidMustacheY = 0x2c, | ||
| 596 | InvalidNoseScale = 0x2e, | ||
| 597 | InvalidNoseType = 0x2f, | ||
| 598 | InvalidNoseY = 0x30, | ||
| 599 | InvalidRegionMove = 0x31, | ||
| 600 | InvalidCreateId = 0x32, | ||
| 601 | InvalidName = 0x33, | ||
| 602 | InvalidType = 0x35, | ||
| 603 | }; | ||
| 604 | |||
| 605 | struct Nickname { | ||
| 606 | static constexpr std::size_t MaxNameSize = 10; | ||
| 607 | std::array<char16_t, MaxNameSize> data; | ||
| 608 | |||
| 609 | // Checks for null or dirty strings | ||
| 610 | bool IsValid() const { | ||
| 611 | if (data[0] == 0) { | ||
| 612 | return false; | ||
| 613 | } | ||
| 614 | |||
| 615 | std::size_t index = 1; | ||
| 616 | while (data[index] != 0) { | ||
| 617 | index++; | ||
| 618 | } | ||
| 619 | while (index < MaxNameSize && data[index] == 0) { | ||
| 620 | index++; | ||
| 621 | } | ||
| 622 | return index == MaxNameSize; | ||
| 623 | } | ||
| 624 | }; | ||
| 625 | static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size"); | ||
| 626 | |||
| 627 | struct DefaultMii { | ||
| 628 | u32 face_type{}; | ||
| 629 | u32 face_color{}; | ||
| 630 | u32 face_wrinkle{}; | ||
| 631 | u32 face_makeup{}; | ||
| 632 | u32 hair_type{}; | ||
| 633 | u32 hair_color{}; | ||
| 634 | u32 hair_flip{}; | ||
| 635 | u32 eye_type{}; | ||
| 636 | u32 eye_color{}; | ||
| 637 | u32 eye_scale{}; | ||
| 638 | u32 eye_aspect{}; | ||
| 639 | u32 eye_rotate{}; | ||
| 640 | u32 eye_x{}; | ||
| 641 | u32 eye_y{}; | ||
| 642 | u32 eyebrow_type{}; | ||
| 643 | u32 eyebrow_color{}; | ||
| 644 | u32 eyebrow_scale{}; | ||
| 645 | u32 eyebrow_aspect{}; | ||
| 646 | u32 eyebrow_rotate{}; | ||
| 647 | u32 eyebrow_x{}; | ||
| 648 | u32 eyebrow_y{}; | ||
| 649 | u32 nose_type{}; | ||
| 650 | u32 nose_scale{}; | ||
| 651 | u32 nose_y{}; | ||
| 652 | u32 mouth_type{}; | ||
| 653 | u32 mouth_color{}; | ||
| 654 | u32 mouth_scale{}; | ||
| 655 | u32 mouth_aspect{}; | ||
| 656 | u32 mouth_y{}; | ||
| 657 | u32 mustache_type{}; | ||
| 658 | u32 beard_type{}; | ||
| 659 | u32 beard_color{}; | ||
| 660 | u32 mustache_scale{}; | ||
| 661 | u32 mustache_y{}; | ||
| 662 | u32 glasses_type{}; | ||
| 663 | u32 glasses_color{}; | ||
| 664 | u32 glasses_scale{}; | ||
| 665 | u32 glasses_y{}; | ||
| 666 | u32 mole_type{}; | ||
| 667 | u32 mole_scale{}; | ||
| 668 | u32 mole_x{}; | ||
| 669 | u32 mole_y{}; | ||
| 670 | u32 height{}; | ||
| 671 | u32 weight{}; | ||
| 672 | u32 gender{}; | ||
| 673 | u32 favorite_color{}; | ||
| 674 | u32 region_move{}; | ||
| 675 | u32 font_region{}; | ||
| 676 | u32 type{}; | ||
| 677 | Nickname nickname; | ||
| 678 | }; | ||
| 679 | static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size."); | ||
| 680 | |||
| 681 | struct DatabaseSessionMetadata { | ||
| 682 | u32 interface_version; | ||
| 683 | u32 magic; | ||
| 684 | u64 update_counter; | ||
| 685 | |||
| 686 | bool IsInterfaceVersionSupported(u32 version) const { | ||
| 687 | return version <= interface_version; | ||
| 688 | } | ||
| 689 | }; | ||
| 690 | |||
| 691 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h new file mode 100644 index 000000000..ddb544c23 --- /dev/null +++ b/src/core/hle/service/mii/mii_util.h | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <random> | ||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/swap.h" | ||
| 11 | #include "common/uuid.h" | ||
| 12 | #include "core/hle/service/mii/mii_types.h" | ||
| 13 | |||
| 14 | namespace Service::Mii { | ||
| 15 | class MiiUtil { | ||
| 16 | public: | ||
| 17 | static u16 CalculateCrc16(const void* data, std::size_t size) { | ||
| 18 | s32 crc{}; | ||
| 19 | for (std::size_t i = 0; i < size; i++) { | ||
| 20 | crc ^= static_cast<const u8*>(data)[i] << 8; | ||
| 21 | for (std::size_t j = 0; j < 8; j++) { | ||
| 22 | crc <<= 1; | ||
| 23 | if ((crc & 0x10000) != 0) { | ||
| 24 | crc = (crc ^ 0x1021) & 0xFFFF; | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| 28 | return Common::swap16(static_cast<u16>(crc)); | ||
| 29 | } | ||
| 30 | |||
| 31 | static Common::UUID MakeCreateId() { | ||
| 32 | return Common::UUID::MakeRandomRFC4122V4(); | ||
| 33 | } | ||
| 34 | |||
| 35 | static Common::UUID GetDeviceId() { | ||
| 36 | // This should be nn::settings::detail::GetMiiAuthorId() | ||
| 37 | return Common::UUID::MakeDefault(); | ||
| 38 | } | ||
| 39 | |||
| 40 | template <typename T> | ||
| 41 | static T GetRandomValue(T min, T max) { | ||
| 42 | std::random_device device; | ||
| 43 | std::mt19937 gen(device()); | ||
| 44 | std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), | ||
| 45 | static_cast<u64>(max)); | ||
| 46 | return static_cast<T>(distribution(gen)); | ||
| 47 | } | ||
| 48 | |||
| 49 | template <typename T> | ||
| 50 | static T GetRandomValue(T max) { | ||
| 51 | return GetRandomValue<T>({}, max); | ||
| 52 | } | ||
| 53 | |||
| 54 | static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) { | ||
| 55 | // TODO: This function needs to check against the font tables | ||
| 56 | return true; | ||
| 57 | } | ||
| 58 | }; | ||
| 59 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h deleted file mode 100644 index c2bec68d4..000000000 --- a/src/core/hle/service/mii/raw_data.h +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include "core/hle/service/mii/types.h" | ||
| 9 | |||
| 10 | namespace Service::Mii::RawData { | ||
| 11 | |||
| 12 | extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; | ||
| 13 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; | ||
| 14 | extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; | ||
| 15 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; | ||
| 16 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup; | ||
| 17 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType; | ||
| 18 | extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor; | ||
| 19 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType; | ||
| 20 | extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor; | ||
| 21 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType; | ||
| 22 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType; | ||
| 23 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType; | ||
| 24 | extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType; | ||
| 25 | |||
| 26 | } // namespace Service::Mii::RawData | ||
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h deleted file mode 100644 index c48d08d79..000000000 --- a/src/core/hle/service/mii/types.h +++ /dev/null | |||
| @@ -1,553 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <type_traits> | ||
| 8 | |||
| 9 | #include "common/bit_field.h" | ||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/uuid.h" | ||
| 13 | |||
| 14 | namespace Service::Mii { | ||
| 15 | |||
| 16 | enum class Age : u32 { | ||
| 17 | Young, | ||
| 18 | Normal, | ||
| 19 | Old, | ||
| 20 | All, | ||
| 21 | }; | ||
| 22 | |||
| 23 | enum class BeardType : u32 { | ||
| 24 | None, | ||
| 25 | Beard1, | ||
| 26 | Beard2, | ||
| 27 | Beard3, | ||
| 28 | Beard4, | ||
| 29 | Beard5, | ||
| 30 | }; | ||
| 31 | |||
| 32 | enum class BeardAndMustacheFlag : u32 { | ||
| 33 | Beard = 1, | ||
| 34 | Mustache, | ||
| 35 | All = Beard | Mustache, | ||
| 36 | }; | ||
| 37 | DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); | ||
| 38 | |||
| 39 | enum class FontRegion : u32 { | ||
| 40 | Standard, | ||
| 41 | China, | ||
| 42 | Korea, | ||
| 43 | Taiwan, | ||
| 44 | }; | ||
| 45 | |||
| 46 | enum class Gender : u32 { | ||
| 47 | Male, | ||
| 48 | Female, | ||
| 49 | All, | ||
| 50 | Maximum = Female, | ||
| 51 | }; | ||
| 52 | |||
| 53 | enum class HairFlip : u32 { | ||
| 54 | Left, | ||
| 55 | Right, | ||
| 56 | Maximum = Right, | ||
| 57 | }; | ||
| 58 | |||
| 59 | enum class MustacheType : u32 { | ||
| 60 | None, | ||
| 61 | Mustache1, | ||
| 62 | Mustache2, | ||
| 63 | Mustache3, | ||
| 64 | Mustache4, | ||
| 65 | Mustache5, | ||
| 66 | }; | ||
| 67 | |||
| 68 | enum class Race : u32 { | ||
| 69 | Black, | ||
| 70 | White, | ||
| 71 | Asian, | ||
| 72 | All, | ||
| 73 | }; | ||
| 74 | |||
| 75 | enum class Source : u32 { | ||
| 76 | Database = 0, | ||
| 77 | Default = 1, | ||
| 78 | Account = 2, | ||
| 79 | Friend = 3, | ||
| 80 | }; | ||
| 81 | |||
| 82 | enum class SourceFlag : u32 { | ||
| 83 | None = 0, | ||
| 84 | Database = 1 << 0, | ||
| 85 | Default = 1 << 1, | ||
| 86 | }; | ||
| 87 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | ||
| 88 | |||
| 89 | // nn::mii::CharInfo | ||
| 90 | struct CharInfo { | ||
| 91 | Common::UUID uuid; | ||
| 92 | std::array<char16_t, 11> name; | ||
| 93 | u8 font_region; | ||
| 94 | u8 favorite_color; | ||
| 95 | u8 gender; | ||
| 96 | u8 height; | ||
| 97 | u8 build; | ||
| 98 | u8 type; | ||
| 99 | u8 region_move; | ||
| 100 | u8 faceline_type; | ||
| 101 | u8 faceline_color; | ||
| 102 | u8 faceline_wrinkle; | ||
| 103 | u8 faceline_make; | ||
| 104 | u8 hair_type; | ||
| 105 | u8 hair_color; | ||
| 106 | u8 hair_flip; | ||
| 107 | u8 eye_type; | ||
| 108 | u8 eye_color; | ||
| 109 | u8 eye_scale; | ||
| 110 | u8 eye_aspect; | ||
| 111 | u8 eye_rotate; | ||
| 112 | u8 eye_x; | ||
| 113 | u8 eye_y; | ||
| 114 | u8 eyebrow_type; | ||
| 115 | u8 eyebrow_color; | ||
| 116 | u8 eyebrow_scale; | ||
| 117 | u8 eyebrow_aspect; | ||
| 118 | u8 eyebrow_rotate; | ||
| 119 | u8 eyebrow_x; | ||
| 120 | u8 eyebrow_y; | ||
| 121 | u8 nose_type; | ||
| 122 | u8 nose_scale; | ||
| 123 | u8 nose_y; | ||
| 124 | u8 mouth_type; | ||
| 125 | u8 mouth_color; | ||
| 126 | u8 mouth_scale; | ||
| 127 | u8 mouth_aspect; | ||
| 128 | u8 mouth_y; | ||
| 129 | u8 beard_color; | ||
| 130 | u8 beard_type; | ||
| 131 | u8 mustache_type; | ||
| 132 | u8 mustache_scale; | ||
| 133 | u8 mustache_y; | ||
| 134 | u8 glasses_type; | ||
| 135 | u8 glasses_color; | ||
| 136 | u8 glasses_scale; | ||
| 137 | u8 glasses_y; | ||
| 138 | u8 mole_type; | ||
| 139 | u8 mole_scale; | ||
| 140 | u8 mole_x; | ||
| 141 | u8 mole_y; | ||
| 142 | u8 padding; | ||
| 143 | }; | ||
| 144 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); | ||
| 145 | static_assert(std::has_unique_object_representations_v<CharInfo>, | ||
| 146 | "All bits of CharInfo must contribute to its value."); | ||
| 147 | |||
| 148 | #pragma pack(push, 4) | ||
| 149 | |||
| 150 | struct MiiInfoElement { | ||
| 151 | MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {} | ||
| 152 | |||
| 153 | CharInfo info{}; | ||
| 154 | Source source{}; | ||
| 155 | }; | ||
| 156 | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); | ||
| 157 | |||
| 158 | struct MiiStoreBitFields { | ||
| 159 | union { | ||
| 160 | u32 word_0{}; | ||
| 161 | |||
| 162 | BitField<0, 8, u32> hair_type; | ||
| 163 | BitField<8, 7, u32> height; | ||
| 164 | BitField<15, 1, u32> mole_type; | ||
| 165 | BitField<16, 7, u32> build; | ||
| 166 | BitField<23, 1, HairFlip> hair_flip; | ||
| 167 | BitField<24, 7, u32> hair_color; | ||
| 168 | BitField<31, 1, u32> type; | ||
| 169 | }; | ||
| 170 | |||
| 171 | union { | ||
| 172 | u32 word_1{}; | ||
| 173 | |||
| 174 | BitField<0, 7, u32> eye_color; | ||
| 175 | BitField<7, 1, Gender> gender; | ||
| 176 | BitField<8, 7, u32> eyebrow_color; | ||
| 177 | BitField<16, 7, u32> mouth_color; | ||
| 178 | BitField<24, 7, u32> beard_color; | ||
| 179 | }; | ||
| 180 | |||
| 181 | union { | ||
| 182 | u32 word_2{}; | ||
| 183 | |||
| 184 | BitField<0, 7, u32> glasses_color; | ||
| 185 | BitField<8, 6, u32> eye_type; | ||
| 186 | BitField<14, 2, u32> region_move; | ||
| 187 | BitField<16, 6, u32> mouth_type; | ||
| 188 | BitField<22, 2, FontRegion> font_region; | ||
| 189 | BitField<24, 5, u32> eye_y; | ||
| 190 | BitField<29, 3, u32> glasses_scale; | ||
| 191 | }; | ||
| 192 | |||
| 193 | union { | ||
| 194 | u32 word_3{}; | ||
| 195 | |||
| 196 | BitField<0, 5, u32> eyebrow_type; | ||
| 197 | BitField<5, 3, MustacheType> mustache_type; | ||
| 198 | BitField<8, 5, u32> nose_type; | ||
| 199 | BitField<13, 3, BeardType> beard_type; | ||
| 200 | BitField<16, 5, u32> nose_y; | ||
| 201 | BitField<21, 3, u32> mouth_aspect; | ||
| 202 | BitField<24, 5, u32> mouth_y; | ||
| 203 | BitField<29, 3, u32> eyebrow_aspect; | ||
| 204 | }; | ||
| 205 | |||
| 206 | union { | ||
| 207 | u32 word_4{}; | ||
| 208 | |||
| 209 | BitField<0, 5, u32> mustache_y; | ||
| 210 | BitField<5, 3, u32> eye_rotate; | ||
| 211 | BitField<8, 5, u32> glasses_y; | ||
| 212 | BitField<13, 3, u32> eye_aspect; | ||
| 213 | BitField<16, 5, u32> mole_x; | ||
| 214 | BitField<21, 3, u32> eye_scale; | ||
| 215 | BitField<24, 5, u32> mole_y; | ||
| 216 | }; | ||
| 217 | |||
| 218 | union { | ||
| 219 | u32 word_5{}; | ||
| 220 | |||
| 221 | BitField<0, 5, u32> glasses_type; | ||
| 222 | BitField<8, 4, u32> favorite_color; | ||
| 223 | BitField<12, 4, u32> faceline_type; | ||
| 224 | BitField<16, 4, u32> faceline_color; | ||
| 225 | BitField<20, 4, u32> faceline_wrinkle; | ||
| 226 | BitField<24, 4, u32> faceline_makeup; | ||
| 227 | BitField<28, 4, u32> eye_x; | ||
| 228 | }; | ||
| 229 | |||
| 230 | union { | ||
| 231 | u32 word_6{}; | ||
| 232 | |||
| 233 | BitField<0, 4, u32> eyebrow_scale; | ||
| 234 | BitField<4, 4, u32> eyebrow_rotate; | ||
| 235 | BitField<8, 4, u32> eyebrow_x; | ||
| 236 | BitField<12, 4, u32> eyebrow_y; | ||
| 237 | BitField<16, 4, u32> nose_scale; | ||
| 238 | BitField<20, 4, u32> mouth_scale; | ||
| 239 | BitField<24, 4, u32> mustache_scale; | ||
| 240 | BitField<28, 4, u32> mole_scale; | ||
| 241 | }; | ||
| 242 | }; | ||
| 243 | static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); | ||
| 244 | static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, | ||
| 245 | "MiiStoreBitFields is not trivially copyable."); | ||
| 246 | |||
| 247 | // This is nn::mii::Ver3StoreData | ||
| 248 | // Based on citra HLE::Applets::MiiData and PretendoNetwork. | ||
| 249 | // https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 | ||
| 250 | // https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 | ||
| 251 | struct Ver3StoreData { | ||
| 252 | u8 version; | ||
| 253 | union { | ||
| 254 | u8 raw; | ||
| 255 | |||
| 256 | BitField<0, 1, u8> allow_copying; | ||
| 257 | BitField<1, 1, u8> profanity_flag; | ||
| 258 | BitField<2, 2, u8> region_lock; | ||
| 259 | BitField<4, 2, u8> character_set; | ||
| 260 | } region_information; | ||
| 261 | u16_be mii_id; | ||
| 262 | u64_be system_id; | ||
| 263 | u32_be specialness_and_creation_date; | ||
| 264 | std::array<u8, 0x6> creator_mac; | ||
| 265 | u16_be padding; | ||
| 266 | union { | ||
| 267 | u16 raw; | ||
| 268 | |||
| 269 | BitField<0, 1, u16> gender; | ||
| 270 | BitField<1, 4, u16> birth_month; | ||
| 271 | BitField<5, 5, u16> birth_day; | ||
| 272 | BitField<10, 4, u16> favorite_color; | ||
| 273 | BitField<14, 1, u16> favorite; | ||
| 274 | } mii_information; | ||
| 275 | std::array<char16_t, 0xA> mii_name; | ||
| 276 | u8 height; | ||
| 277 | u8 build; | ||
| 278 | union { | ||
| 279 | u8 raw; | ||
| 280 | |||
| 281 | BitField<0, 1, u8> disable_sharing; | ||
| 282 | BitField<1, 4, u8> face_shape; | ||
| 283 | BitField<5, 3, u8> skin_color; | ||
| 284 | } appearance_bits1; | ||
| 285 | union { | ||
| 286 | u8 raw; | ||
| 287 | |||
| 288 | BitField<0, 4, u8> wrinkles; | ||
| 289 | BitField<4, 4, u8> makeup; | ||
| 290 | } appearance_bits2; | ||
| 291 | u8 hair_style; | ||
| 292 | union { | ||
| 293 | u8 raw; | ||
| 294 | |||
| 295 | BitField<0, 3, u8> hair_color; | ||
| 296 | BitField<3, 1, u8> flip_hair; | ||
| 297 | } appearance_bits3; | ||
| 298 | union { | ||
| 299 | u32 raw; | ||
| 300 | |||
| 301 | BitField<0, 6, u32> eye_type; | ||
| 302 | BitField<6, 3, u32> eye_color; | ||
| 303 | BitField<9, 4, u32> eye_scale; | ||
| 304 | BitField<13, 3, u32> eye_vertical_stretch; | ||
| 305 | BitField<16, 5, u32> eye_rotation; | ||
| 306 | BitField<21, 4, u32> eye_spacing; | ||
| 307 | BitField<25, 5, u32> eye_y_position; | ||
| 308 | } appearance_bits4; | ||
| 309 | union { | ||
| 310 | u32 raw; | ||
| 311 | |||
| 312 | BitField<0, 5, u32> eyebrow_style; | ||
| 313 | BitField<5, 3, u32> eyebrow_color; | ||
| 314 | BitField<8, 4, u32> eyebrow_scale; | ||
| 315 | BitField<12, 3, u32> eyebrow_yscale; | ||
| 316 | BitField<16, 4, u32> eyebrow_rotation; | ||
| 317 | BitField<21, 4, u32> eyebrow_spacing; | ||
| 318 | BitField<25, 5, u32> eyebrow_y_position; | ||
| 319 | } appearance_bits5; | ||
| 320 | union { | ||
| 321 | u16 raw; | ||
| 322 | |||
| 323 | BitField<0, 5, u16> nose_type; | ||
| 324 | BitField<5, 4, u16> nose_scale; | ||
| 325 | BitField<9, 5, u16> nose_y_position; | ||
| 326 | } appearance_bits6; | ||
| 327 | union { | ||
| 328 | u16 raw; | ||
| 329 | |||
| 330 | BitField<0, 6, u16> mouth_type; | ||
| 331 | BitField<6, 3, u16> mouth_color; | ||
| 332 | BitField<9, 4, u16> mouth_scale; | ||
| 333 | BitField<13, 3, u16> mouth_horizontal_stretch; | ||
| 334 | } appearance_bits7; | ||
| 335 | union { | ||
| 336 | u8 raw; | ||
| 337 | |||
| 338 | BitField<0, 5, u8> mouth_y_position; | ||
| 339 | BitField<5, 3, u8> mustache_type; | ||
| 340 | } appearance_bits8; | ||
| 341 | u8 allow_copying; | ||
| 342 | union { | ||
| 343 | u16 raw; | ||
| 344 | |||
| 345 | BitField<0, 3, u16> bear_type; | ||
| 346 | BitField<3, 3, u16> facial_hair_color; | ||
| 347 | BitField<6, 4, u16> mustache_scale; | ||
| 348 | BitField<10, 5, u16> mustache_y_position; | ||
| 349 | } appearance_bits9; | ||
| 350 | union { | ||
| 351 | u16 raw; | ||
| 352 | |||
| 353 | BitField<0, 4, u16> glasses_type; | ||
| 354 | BitField<4, 3, u16> glasses_color; | ||
| 355 | BitField<7, 4, u16> glasses_scale; | ||
| 356 | BitField<11, 5, u16> glasses_y_position; | ||
| 357 | } appearance_bits10; | ||
| 358 | union { | ||
| 359 | u16 raw; | ||
| 360 | |||
| 361 | BitField<0, 1, u16> mole_enabled; | ||
| 362 | BitField<1, 4, u16> mole_scale; | ||
| 363 | BitField<5, 5, u16> mole_x_position; | ||
| 364 | BitField<10, 5, u16> mole_y_position; | ||
| 365 | } appearance_bits11; | ||
| 366 | |||
| 367 | std::array<u16_le, 0xA> author_name; | ||
| 368 | INSERT_PADDING_BYTES(0x2); | ||
| 369 | u16_be crc; | ||
| 370 | }; | ||
| 371 | static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); | ||
| 372 | |||
| 373 | struct NfpStoreDataExtension { | ||
| 374 | u8 faceline_color; | ||
| 375 | u8 hair_color; | ||
| 376 | u8 eye_color; | ||
| 377 | u8 eyebrow_color; | ||
| 378 | u8 mouth_color; | ||
| 379 | u8 beard_color; | ||
| 380 | u8 glass_color; | ||
| 381 | u8 glass_type; | ||
| 382 | }; | ||
| 383 | static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); | ||
| 384 | |||
| 385 | constexpr std::array<u8, 0x10> Ver3FacelineColorTable{ | ||
| 386 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, | ||
| 387 | }; | ||
| 388 | |||
| 389 | constexpr std::array<u8, 100> Ver3HairColorTable{ | ||
| 390 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, | ||
| 391 | 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 392 | 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, | ||
| 393 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, | ||
| 394 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, | ||
| 395 | 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, | ||
| 396 | }; | ||
| 397 | |||
| 398 | constexpr std::array<u8, 100> Ver3EyeColorTable{ | ||
| 399 | 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, | ||
| 400 | 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 401 | 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, | ||
| 402 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 403 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, | ||
| 404 | 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, | ||
| 405 | }; | ||
| 406 | |||
| 407 | constexpr std::array<u8, 100> Ver3MouthlineColorTable{ | ||
| 408 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 409 | 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 410 | 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 411 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 412 | 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, | ||
| 413 | 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, | ||
| 414 | }; | ||
| 415 | |||
| 416 | constexpr std::array<u8, 100> Ver3GlassColorTable{ | ||
| 417 | 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, | ||
| 418 | 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 419 | 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, | ||
| 420 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, | ||
| 421 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, | ||
| 422 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 423 | }; | ||
| 424 | |||
| 425 | constexpr std::array<u8, 20> Ver3GlassTypeTable{ | ||
| 426 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, | ||
| 427 | 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, | ||
| 428 | }; | ||
| 429 | |||
| 430 | struct MiiStoreData { | ||
| 431 | using Name = std::array<char16_t, 10>; | ||
| 432 | |||
| 433 | MiiStoreData(); | ||
| 434 | MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, | ||
| 435 | const Common::UUID& user_id); | ||
| 436 | |||
| 437 | // This corresponds to the above structure MiiStoreBitFields. I did it like this because the | ||
| 438 | // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is | ||
| 439 | // not suitable for our uses. | ||
| 440 | struct { | ||
| 441 | std::array<u8, 0x1C> data{}; | ||
| 442 | static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); | ||
| 443 | |||
| 444 | Name name{}; | ||
| 445 | Common::UUID uuid{}; | ||
| 446 | } data; | ||
| 447 | |||
| 448 | u16 data_crc{}; | ||
| 449 | u16 device_crc{}; | ||
| 450 | }; | ||
| 451 | static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); | ||
| 452 | |||
| 453 | struct MiiStoreDataElement { | ||
| 454 | MiiStoreData data{}; | ||
| 455 | Source source{}; | ||
| 456 | }; | ||
| 457 | static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); | ||
| 458 | |||
| 459 | struct MiiDatabase { | ||
| 460 | u32 magic{}; // 'NFDB' | ||
| 461 | std::array<MiiStoreData, 0x64> miis{}; | ||
| 462 | INSERT_PADDING_BYTES(1); | ||
| 463 | u8 count{}; | ||
| 464 | u16 crc{}; | ||
| 465 | }; | ||
| 466 | static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); | ||
| 467 | |||
| 468 | struct RandomMiiValues { | ||
| 469 | std::array<u8, 0xbc> values{}; | ||
| 470 | }; | ||
| 471 | static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); | ||
| 472 | |||
| 473 | struct RandomMiiData4 { | ||
| 474 | Gender gender{}; | ||
| 475 | Age age{}; | ||
| 476 | Race race{}; | ||
| 477 | u32 values_count{}; | ||
| 478 | std::array<u32, 47> values{}; | ||
| 479 | }; | ||
| 480 | static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); | ||
| 481 | |||
| 482 | struct RandomMiiData3 { | ||
| 483 | u32 arg_1; | ||
| 484 | u32 arg_2; | ||
| 485 | u32 values_count; | ||
| 486 | std::array<u32, 47> values{}; | ||
| 487 | }; | ||
| 488 | static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); | ||
| 489 | |||
| 490 | struct RandomMiiData2 { | ||
| 491 | u32 arg_1; | ||
| 492 | u32 values_count; | ||
| 493 | std::array<u32, 47> values{}; | ||
| 494 | }; | ||
| 495 | static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); | ||
| 496 | |||
| 497 | struct DefaultMii { | ||
| 498 | u32 face_type{}; | ||
| 499 | u32 face_color{}; | ||
| 500 | u32 face_wrinkle{}; | ||
| 501 | u32 face_makeup{}; | ||
| 502 | u32 hair_type{}; | ||
| 503 | u32 hair_color{}; | ||
| 504 | u32 hair_flip{}; | ||
| 505 | u32 eye_type{}; | ||
| 506 | u32 eye_color{}; | ||
| 507 | u32 eye_scale{}; | ||
| 508 | u32 eye_aspect{}; | ||
| 509 | u32 eye_rotate{}; | ||
| 510 | u32 eye_x{}; | ||
| 511 | u32 eye_y{}; | ||
| 512 | u32 eyebrow_type{}; | ||
| 513 | u32 eyebrow_color{}; | ||
| 514 | u32 eyebrow_scale{}; | ||
| 515 | u32 eyebrow_aspect{}; | ||
| 516 | u32 eyebrow_rotate{}; | ||
| 517 | u32 eyebrow_x{}; | ||
| 518 | u32 eyebrow_y{}; | ||
| 519 | u32 nose_type{}; | ||
| 520 | u32 nose_scale{}; | ||
| 521 | u32 nose_y{}; | ||
| 522 | u32 mouth_type{}; | ||
| 523 | u32 mouth_color{}; | ||
| 524 | u32 mouth_scale{}; | ||
| 525 | u32 mouth_aspect{}; | ||
| 526 | u32 mouth_y{}; | ||
| 527 | u32 mustache_type{}; | ||
| 528 | u32 beard_type{}; | ||
| 529 | u32 beard_color{}; | ||
| 530 | u32 mustache_scale{}; | ||
| 531 | u32 mustache_y{}; | ||
| 532 | u32 glasses_type{}; | ||
| 533 | u32 glasses_color{}; | ||
| 534 | u32 glasses_scale{}; | ||
| 535 | u32 glasses_y{}; | ||
| 536 | u32 mole_type{}; | ||
| 537 | u32 mole_scale{}; | ||
| 538 | u32 mole_x{}; | ||
| 539 | u32 mole_y{}; | ||
| 540 | u32 height{}; | ||
| 541 | u32 weight{}; | ||
| 542 | Gender gender{}; | ||
| 543 | u32 favorite_color{}; | ||
| 544 | u32 region{}; | ||
| 545 | FontRegion font_region{}; | ||
| 546 | u32 type{}; | ||
| 547 | INSERT_PADDING_WORDS(5); | ||
| 548 | }; | ||
| 549 | static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); | ||
| 550 | |||
| 551 | #pragma pack(pop) | ||
| 552 | |||
| 553 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp new file mode 100644 index 000000000..bb948c628 --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.cpp | |||
| @@ -0,0 +1,482 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/types/char_info.h" | ||
| 5 | #include "core/hle/service/mii/types/store_data.h" | ||
| 6 | |||
| 7 | namespace Service::Mii { | ||
| 8 | |||
| 9 | void CharInfo::SetFromStoreData(const StoreData& store_data) { | ||
| 10 | name = store_data.GetNickname(); | ||
| 11 | null_terminator = '\0'; | ||
| 12 | create_id = store_data.GetCreateId(); | ||
| 13 | font_region = store_data.GetFontRegion(); | ||
| 14 | favorite_color = store_data.GetFavoriteColor(); | ||
| 15 | gender = store_data.GetGender(); | ||
| 16 | height = store_data.GetHeight(); | ||
| 17 | build = store_data.GetBuild(); | ||
| 18 | type = store_data.GetType(); | ||
| 19 | region_move = store_data.GetRegionMove(); | ||
| 20 | faceline_type = store_data.GetFacelineType(); | ||
| 21 | faceline_color = store_data.GetFacelineColor(); | ||
| 22 | faceline_wrinkle = store_data.GetFacelineWrinkle(); | ||
| 23 | faceline_make = store_data.GetFacelineMake(); | ||
| 24 | hair_type = store_data.GetHairType(); | ||
| 25 | hair_color = store_data.GetHairColor(); | ||
| 26 | hair_flip = store_data.GetHairFlip(); | ||
| 27 | eye_type = store_data.GetEyeType(); | ||
| 28 | eye_color = store_data.GetEyeColor(); | ||
| 29 | eye_scale = store_data.GetEyeScale(); | ||
| 30 | eye_aspect = store_data.GetEyeAspect(); | ||
| 31 | eye_rotate = store_data.GetEyeRotate(); | ||
| 32 | eye_x = store_data.GetEyeX(); | ||
| 33 | eye_y = store_data.GetEyeY(); | ||
| 34 | eyebrow_type = store_data.GetEyebrowType(); | ||
| 35 | eyebrow_color = store_data.GetEyebrowColor(); | ||
| 36 | eyebrow_scale = store_data.GetEyebrowScale(); | ||
| 37 | eyebrow_aspect = store_data.GetEyebrowAspect(); | ||
| 38 | eyebrow_rotate = store_data.GetEyebrowRotate(); | ||
| 39 | eyebrow_x = store_data.GetEyebrowX(); | ||
| 40 | eyebrow_y = store_data.GetEyebrowY(); | ||
| 41 | nose_type = store_data.GetNoseType(); | ||
| 42 | nose_scale = store_data.GetNoseScale(); | ||
| 43 | nose_y = store_data.GetNoseY(); | ||
| 44 | mouth_type = store_data.GetMouthType(); | ||
| 45 | mouth_color = store_data.GetMouthColor(); | ||
| 46 | mouth_scale = store_data.GetMouthScale(); | ||
| 47 | mouth_aspect = store_data.GetMouthAspect(); | ||
| 48 | mouth_y = store_data.GetMouthY(); | ||
| 49 | beard_color = store_data.GetBeardColor(); | ||
| 50 | beard_type = store_data.GetBeardType(); | ||
| 51 | mustache_type = store_data.GetMustacheType(); | ||
| 52 | mustache_scale = store_data.GetMustacheScale(); | ||
| 53 | mustache_y = store_data.GetMustacheY(); | ||
| 54 | glass_type = store_data.GetGlassType(); | ||
| 55 | glass_color = store_data.GetGlassColor(); | ||
| 56 | glass_scale = store_data.GetGlassScale(); | ||
| 57 | glass_y = store_data.GetGlassY(); | ||
| 58 | mole_type = store_data.GetMoleType(); | ||
| 59 | mole_scale = store_data.GetMoleScale(); | ||
| 60 | mole_x = store_data.GetMoleX(); | ||
| 61 | mole_y = store_data.GetMoleY(); | ||
| 62 | padding = '\0'; | ||
| 63 | } | ||
| 64 | |||
| 65 | ValidationResult CharInfo::Verify() const { | ||
| 66 | if (!create_id.IsValid()) { | ||
| 67 | return ValidationResult::InvalidCreateId; | ||
| 68 | } | ||
| 69 | if (!name.IsValid()) { | ||
| 70 | return ValidationResult::InvalidName; | ||
| 71 | } | ||
| 72 | if (font_region > FontRegion::Max) { | ||
| 73 | return ValidationResult::InvalidFont; | ||
| 74 | } | ||
| 75 | if (favorite_color > FavoriteColor::Max) { | ||
| 76 | return ValidationResult::InvalidColor; | ||
| 77 | } | ||
| 78 | if (gender > Gender::Max) { | ||
| 79 | return ValidationResult::InvalidGender; | ||
| 80 | } | ||
| 81 | if (height > MaxHeight) { | ||
| 82 | return ValidationResult::InvalidHeight; | ||
| 83 | } | ||
| 84 | if (build > MaxBuild) { | ||
| 85 | return ValidationResult::InvalidBuild; | ||
| 86 | } | ||
| 87 | if (type > MaxType) { | ||
| 88 | return ValidationResult::InvalidType; | ||
| 89 | } | ||
| 90 | if (region_move > MaxRegionMove) { | ||
| 91 | return ValidationResult::InvalidRegionMove; | ||
| 92 | } | ||
| 93 | if (faceline_type > FacelineType::Max) { | ||
| 94 | return ValidationResult::InvalidFacelineType; | ||
| 95 | } | ||
| 96 | if (faceline_color > FacelineColor::Max) { | ||
| 97 | return ValidationResult::InvalidFacelineColor; | ||
| 98 | } | ||
| 99 | if (faceline_wrinkle > FacelineWrinkle::Max) { | ||
| 100 | return ValidationResult::InvalidFacelineWrinkle; | ||
| 101 | } | ||
| 102 | if (faceline_make > FacelineMake::Max) { | ||
| 103 | return ValidationResult::InvalidFacelineMake; | ||
| 104 | } | ||
| 105 | if (hair_type > HairType::Max) { | ||
| 106 | return ValidationResult::InvalidHairType; | ||
| 107 | } | ||
| 108 | if (hair_color > CommonColor::Max) { | ||
| 109 | return ValidationResult::InvalidHairColor; | ||
| 110 | } | ||
| 111 | if (hair_flip > HairFlip::Max) { | ||
| 112 | return ValidationResult::InvalidHairFlip; | ||
| 113 | } | ||
| 114 | if (eye_type > EyeType::Max) { | ||
| 115 | return ValidationResult::InvalidEyeType; | ||
| 116 | } | ||
| 117 | if (eye_color > CommonColor::Max) { | ||
| 118 | return ValidationResult::InvalidEyeColor; | ||
| 119 | } | ||
| 120 | if (eye_scale > MaxEyeScale) { | ||
| 121 | return ValidationResult::InvalidEyeScale; | ||
| 122 | } | ||
| 123 | if (eye_aspect > MaxEyeAspect) { | ||
| 124 | return ValidationResult::InvalidEyeAspect; | ||
| 125 | } | ||
| 126 | if (eye_rotate > MaxEyeX) { | ||
| 127 | return ValidationResult::InvalidEyeRotate; | ||
| 128 | } | ||
| 129 | if (eye_x > MaxEyeX) { | ||
| 130 | return ValidationResult::InvalidEyeX; | ||
| 131 | } | ||
| 132 | if (eye_y > MaxEyeY) { | ||
| 133 | return ValidationResult::InvalidEyeY; | ||
| 134 | } | ||
| 135 | if (eyebrow_type > EyebrowType::Max) { | ||
| 136 | return ValidationResult::InvalidEyebrowType; | ||
| 137 | } | ||
| 138 | if (eyebrow_color > CommonColor::Max) { | ||
| 139 | return ValidationResult::InvalidEyebrowColor; | ||
| 140 | } | ||
| 141 | if (eyebrow_scale > MaxEyebrowScale) { | ||
| 142 | return ValidationResult::InvalidEyebrowScale; | ||
| 143 | } | ||
| 144 | if (eyebrow_aspect > MaxEyebrowAspect) { | ||
| 145 | return ValidationResult::InvalidEyebrowAspect; | ||
| 146 | } | ||
| 147 | if (eyebrow_rotate > MaxEyebrowRotate) { | ||
| 148 | return ValidationResult::InvalidEyebrowRotate; | ||
| 149 | } | ||
| 150 | if (eyebrow_x > MaxEyebrowX) { | ||
| 151 | return ValidationResult::InvalidEyebrowX; | ||
| 152 | } | ||
| 153 | if (eyebrow_y > MaxEyebrowY) { | ||
| 154 | return ValidationResult::InvalidEyebrowY; | ||
| 155 | } | ||
| 156 | if (nose_type > NoseType::Max) { | ||
| 157 | return ValidationResult::InvalidNoseType; | ||
| 158 | } | ||
| 159 | if (nose_scale > MaxNoseScale) { | ||
| 160 | return ValidationResult::InvalidNoseScale; | ||
| 161 | } | ||
| 162 | if (nose_y > MaxNoseY) { | ||
| 163 | return ValidationResult::InvalidNoseY; | ||
| 164 | } | ||
| 165 | if (mouth_type > MouthType::Max) { | ||
| 166 | return ValidationResult::InvalidMouthType; | ||
| 167 | } | ||
| 168 | if (mouth_color > CommonColor::Max) { | ||
| 169 | return ValidationResult::InvalidMouthColor; | ||
| 170 | } | ||
| 171 | if (mouth_scale > MaxMouthScale) { | ||
| 172 | return ValidationResult::InvalidMouthScale; | ||
| 173 | } | ||
| 174 | if (mouth_aspect > MaxMoutAspect) { | ||
| 175 | return ValidationResult::InvalidMouthAspect; | ||
| 176 | } | ||
| 177 | if (mouth_y > MaxMouthY) { | ||
| 178 | return ValidationResult::InvalidMoleY; | ||
| 179 | } | ||
| 180 | if (beard_color > CommonColor::Max) { | ||
| 181 | return ValidationResult::InvalidBeardColor; | ||
| 182 | } | ||
| 183 | if (beard_type > BeardType::Max) { | ||
| 184 | return ValidationResult::InvalidBeardType; | ||
| 185 | } | ||
| 186 | if (mustache_type > MustacheType::Max) { | ||
| 187 | return ValidationResult::InvalidMustacheType; | ||
| 188 | } | ||
| 189 | if (mustache_scale > MaxMustacheScale) { | ||
| 190 | return ValidationResult::InvalidMustacheScale; | ||
| 191 | } | ||
| 192 | if (mustache_y > MasMustacheY) { | ||
| 193 | return ValidationResult::InvalidMustacheY; | ||
| 194 | } | ||
| 195 | if (glass_type > GlassType::Max) { | ||
| 196 | return ValidationResult::InvalidGlassType; | ||
| 197 | } | ||
| 198 | if (glass_color > CommonColor::Max) { | ||
| 199 | return ValidationResult::InvalidGlassColor; | ||
| 200 | } | ||
| 201 | if (glass_scale > MaxGlassScale) { | ||
| 202 | return ValidationResult::InvalidGlassScale; | ||
| 203 | } | ||
| 204 | if (glass_y > MaxGlassY) { | ||
| 205 | return ValidationResult::InvalidGlassY; | ||
| 206 | } | ||
| 207 | if (mole_type > MoleType::Max) { | ||
| 208 | return ValidationResult::InvalidMoleType; | ||
| 209 | } | ||
| 210 | if (mole_scale > MaxMoleScale) { | ||
| 211 | return ValidationResult::InvalidMoleScale; | ||
| 212 | } | ||
| 213 | if (mole_x > MaxMoleX) { | ||
| 214 | return ValidationResult::InvalidMoleX; | ||
| 215 | } | ||
| 216 | if (mole_y > MaxMoleY) { | ||
| 217 | return ValidationResult::InvalidMoleY; | ||
| 218 | } | ||
| 219 | return ValidationResult::NoErrors; | ||
| 220 | } | ||
| 221 | |||
| 222 | Common::UUID CharInfo::GetCreateId() const { | ||
| 223 | return create_id; | ||
| 224 | } | ||
| 225 | |||
| 226 | Nickname CharInfo::GetNickname() const { | ||
| 227 | return name; | ||
| 228 | } | ||
| 229 | |||
| 230 | FontRegion CharInfo::GetFontRegion() const { | ||
| 231 | return font_region; | ||
| 232 | } | ||
| 233 | |||
| 234 | FavoriteColor CharInfo::GetFavoriteColor() const { | ||
| 235 | return favorite_color; | ||
| 236 | } | ||
| 237 | |||
| 238 | Gender CharInfo::GetGender() const { | ||
| 239 | return gender; | ||
| 240 | } | ||
| 241 | |||
| 242 | u8 CharInfo::GetHeight() const { | ||
| 243 | return height; | ||
| 244 | } | ||
| 245 | |||
| 246 | u8 CharInfo::GetBuild() const { | ||
| 247 | return build; | ||
| 248 | } | ||
| 249 | |||
| 250 | u8 CharInfo::GetType() const { | ||
| 251 | return type; | ||
| 252 | } | ||
| 253 | |||
| 254 | u8 CharInfo::GetRegionMove() const { | ||
| 255 | return region_move; | ||
| 256 | } | ||
| 257 | |||
| 258 | FacelineType CharInfo::GetFacelineType() const { | ||
| 259 | return faceline_type; | ||
| 260 | } | ||
| 261 | |||
| 262 | FacelineColor CharInfo::GetFacelineColor() const { | ||
| 263 | return faceline_color; | ||
| 264 | } | ||
| 265 | |||
| 266 | FacelineWrinkle CharInfo::GetFacelineWrinkle() const { | ||
| 267 | return faceline_wrinkle; | ||
| 268 | } | ||
| 269 | |||
| 270 | FacelineMake CharInfo::GetFacelineMake() const { | ||
| 271 | return faceline_make; | ||
| 272 | } | ||
| 273 | |||
| 274 | HairType CharInfo::GetHairType() const { | ||
| 275 | return hair_type; | ||
| 276 | } | ||
| 277 | |||
| 278 | CommonColor CharInfo::GetHairColor() const { | ||
| 279 | return hair_color; | ||
| 280 | } | ||
| 281 | |||
| 282 | HairFlip CharInfo::GetHairFlip() const { | ||
| 283 | return hair_flip; | ||
| 284 | } | ||
| 285 | |||
| 286 | EyeType CharInfo::GetEyeType() const { | ||
| 287 | return eye_type; | ||
| 288 | } | ||
| 289 | |||
| 290 | CommonColor CharInfo::GetEyeColor() const { | ||
| 291 | return eye_color; | ||
| 292 | } | ||
| 293 | |||
| 294 | u8 CharInfo::GetEyeScale() const { | ||
| 295 | return eye_scale; | ||
| 296 | } | ||
| 297 | |||
| 298 | u8 CharInfo::GetEyeAspect() const { | ||
| 299 | return eye_aspect; | ||
| 300 | } | ||
| 301 | |||
| 302 | u8 CharInfo::GetEyeRotate() const { | ||
| 303 | return eye_rotate; | ||
| 304 | } | ||
| 305 | |||
| 306 | u8 CharInfo::GetEyeX() const { | ||
| 307 | return eye_x; | ||
| 308 | } | ||
| 309 | |||
| 310 | u8 CharInfo::GetEyeY() const { | ||
| 311 | return eye_y; | ||
| 312 | } | ||
| 313 | |||
| 314 | EyebrowType CharInfo::GetEyebrowType() const { | ||
| 315 | return eyebrow_type; | ||
| 316 | } | ||
| 317 | |||
| 318 | CommonColor CharInfo::GetEyebrowColor() const { | ||
| 319 | return eyebrow_color; | ||
| 320 | } | ||
| 321 | |||
| 322 | u8 CharInfo::GetEyebrowScale() const { | ||
| 323 | return eyebrow_scale; | ||
| 324 | } | ||
| 325 | |||
| 326 | u8 CharInfo::GetEyebrowAspect() const { | ||
| 327 | return eyebrow_aspect; | ||
| 328 | } | ||
| 329 | |||
| 330 | u8 CharInfo::GetEyebrowRotate() const { | ||
| 331 | return eyebrow_rotate; | ||
| 332 | } | ||
| 333 | |||
| 334 | u8 CharInfo::GetEyebrowX() const { | ||
| 335 | return eyebrow_x; | ||
| 336 | } | ||
| 337 | |||
| 338 | u8 CharInfo::GetEyebrowY() const { | ||
| 339 | return eyebrow_y; | ||
| 340 | } | ||
| 341 | |||
| 342 | NoseType CharInfo::GetNoseType() const { | ||
| 343 | return nose_type; | ||
| 344 | } | ||
| 345 | |||
| 346 | u8 CharInfo::GetNoseScale() const { | ||
| 347 | return nose_scale; | ||
| 348 | } | ||
| 349 | |||
| 350 | u8 CharInfo::GetNoseY() const { | ||
| 351 | return nose_y; | ||
| 352 | } | ||
| 353 | |||
| 354 | MouthType CharInfo::GetMouthType() const { | ||
| 355 | return mouth_type; | ||
| 356 | } | ||
| 357 | |||
| 358 | CommonColor CharInfo::GetMouthColor() const { | ||
| 359 | return mouth_color; | ||
| 360 | } | ||
| 361 | |||
| 362 | u8 CharInfo::GetMouthScale() const { | ||
| 363 | return mouth_scale; | ||
| 364 | } | ||
| 365 | |||
| 366 | u8 CharInfo::GetMouthAspect() const { | ||
| 367 | return mouth_aspect; | ||
| 368 | } | ||
| 369 | |||
| 370 | u8 CharInfo::GetMouthY() const { | ||
| 371 | return mouth_y; | ||
| 372 | } | ||
| 373 | |||
| 374 | CommonColor CharInfo::GetBeardColor() const { | ||
| 375 | return beard_color; | ||
| 376 | } | ||
| 377 | |||
| 378 | BeardType CharInfo::GetBeardType() const { | ||
| 379 | return beard_type; | ||
| 380 | } | ||
| 381 | |||
| 382 | MustacheType CharInfo::GetMustacheType() const { | ||
| 383 | return mustache_type; | ||
| 384 | } | ||
| 385 | |||
| 386 | u8 CharInfo::GetMustacheScale() const { | ||
| 387 | return mustache_scale; | ||
| 388 | } | ||
| 389 | |||
| 390 | u8 CharInfo::GetMustacheY() const { | ||
| 391 | return mustache_y; | ||
| 392 | } | ||
| 393 | |||
| 394 | GlassType CharInfo::GetGlassType() const { | ||
| 395 | return glass_type; | ||
| 396 | } | ||
| 397 | |||
| 398 | CommonColor CharInfo::GetGlassColor() const { | ||
| 399 | return glass_color; | ||
| 400 | } | ||
| 401 | |||
| 402 | u8 CharInfo::GetGlassScale() const { | ||
| 403 | return glass_scale; | ||
| 404 | } | ||
| 405 | |||
| 406 | u8 CharInfo::GetGlassY() const { | ||
| 407 | return glass_y; | ||
| 408 | } | ||
| 409 | |||
| 410 | MoleType CharInfo::GetMoleType() const { | ||
| 411 | return mole_type; | ||
| 412 | } | ||
| 413 | |||
| 414 | u8 CharInfo::GetMoleScale() const { | ||
| 415 | return mole_scale; | ||
| 416 | } | ||
| 417 | |||
| 418 | u8 CharInfo::GetMoleX() const { | ||
| 419 | return mole_x; | ||
| 420 | } | ||
| 421 | |||
| 422 | u8 CharInfo::GetMoleY() const { | ||
| 423 | return mole_y; | ||
| 424 | } | ||
| 425 | |||
| 426 | bool CharInfo::operator==(const CharInfo& info) { | ||
| 427 | bool is_identical = info.Verify() == ValidationResult::NoErrors; | ||
| 428 | is_identical &= name.data == info.GetNickname().data; | ||
| 429 | is_identical &= create_id == info.GetCreateId(); | ||
| 430 | is_identical &= font_region == info.GetFontRegion(); | ||
| 431 | is_identical &= favorite_color == info.GetFavoriteColor(); | ||
| 432 | is_identical &= gender == info.GetGender(); | ||
| 433 | is_identical &= height == info.GetHeight(); | ||
| 434 | is_identical &= build == info.GetBuild(); | ||
| 435 | is_identical &= type == info.GetType(); | ||
| 436 | is_identical &= region_move == info.GetRegionMove(); | ||
| 437 | is_identical &= faceline_type == info.GetFacelineType(); | ||
| 438 | is_identical &= faceline_color == info.GetFacelineColor(); | ||
| 439 | is_identical &= faceline_wrinkle == info.GetFacelineWrinkle(); | ||
| 440 | is_identical &= faceline_make == info.GetFacelineMake(); | ||
| 441 | is_identical &= hair_type == info.GetHairType(); | ||
| 442 | is_identical &= hair_color == info.GetHairColor(); | ||
| 443 | is_identical &= hair_flip == info.GetHairFlip(); | ||
| 444 | is_identical &= eye_type == info.GetEyeType(); | ||
| 445 | is_identical &= eye_color == info.GetEyeColor(); | ||
| 446 | is_identical &= eye_scale == info.GetEyeScale(); | ||
| 447 | is_identical &= eye_aspect == info.GetEyeAspect(); | ||
| 448 | is_identical &= eye_rotate == info.GetEyeRotate(); | ||
| 449 | is_identical &= eye_x == info.GetEyeX(); | ||
| 450 | is_identical &= eye_y == info.GetEyeY(); | ||
| 451 | is_identical &= eyebrow_type == info.GetEyebrowType(); | ||
| 452 | is_identical &= eyebrow_color == info.GetEyebrowColor(); | ||
| 453 | is_identical &= eyebrow_scale == info.GetEyebrowScale(); | ||
| 454 | is_identical &= eyebrow_aspect == info.GetEyebrowAspect(); | ||
| 455 | is_identical &= eyebrow_rotate == info.GetEyebrowRotate(); | ||
| 456 | is_identical &= eyebrow_x == info.GetEyebrowX(); | ||
| 457 | is_identical &= eyebrow_y == info.GetEyebrowY(); | ||
| 458 | is_identical &= nose_type == info.GetNoseType(); | ||
| 459 | is_identical &= nose_scale == info.GetNoseScale(); | ||
| 460 | is_identical &= nose_y == info.GetNoseY(); | ||
| 461 | is_identical &= mouth_type == info.GetMouthType(); | ||
| 462 | is_identical &= mouth_color == info.GetMouthColor(); | ||
| 463 | is_identical &= mouth_scale == info.GetMouthScale(); | ||
| 464 | is_identical &= mouth_aspect == info.GetMouthAspect(); | ||
| 465 | is_identical &= mouth_y == info.GetMouthY(); | ||
| 466 | is_identical &= beard_color == info.GetBeardColor(); | ||
| 467 | is_identical &= beard_type == info.GetBeardType(); | ||
| 468 | is_identical &= mustache_type == info.GetMustacheType(); | ||
| 469 | is_identical &= mustache_scale == info.GetMustacheScale(); | ||
| 470 | is_identical &= mustache_y == info.GetMustacheY(); | ||
| 471 | is_identical &= glass_type == info.GetGlassType(); | ||
| 472 | is_identical &= glass_color == info.GetGlassColor(); | ||
| 473 | is_identical &= glass_scale == info.GetGlassScale(); | ||
| 474 | is_identical &= glass_y == info.GetGlassY(); | ||
| 475 | is_identical &= mole_type == info.GetMoleType(); | ||
| 476 | is_identical &= mole_scale == info.GetMoleScale(); | ||
| 477 | is_identical &= mole_x == info.GetMoleX(); | ||
| 478 | is_identical &= mole_y == info.GetMoleY(); | ||
| 479 | return is_identical; | ||
| 480 | } | ||
| 481 | |||
| 482 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h new file mode 100644 index 000000000..d069b221f --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.h | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | class StoreData; | ||
| 10 | |||
| 11 | // This is nn::mii::detail::CharInfoRaw | ||
| 12 | class CharInfo { | ||
| 13 | public: | ||
| 14 | void SetFromStoreData(const StoreData& store_data_raw); | ||
| 15 | |||
| 16 | ValidationResult Verify() const; | ||
| 17 | |||
| 18 | Common::UUID GetCreateId() const; | ||
| 19 | Nickname GetNickname() const; | ||
| 20 | FontRegion GetFontRegion() const; | ||
| 21 | FavoriteColor GetFavoriteColor() const; | ||
| 22 | Gender GetGender() const; | ||
| 23 | u8 GetHeight() const; | ||
| 24 | u8 GetBuild() const; | ||
| 25 | u8 GetType() const; | ||
| 26 | u8 GetRegionMove() const; | ||
| 27 | FacelineType GetFacelineType() const; | ||
| 28 | FacelineColor GetFacelineColor() const; | ||
| 29 | FacelineWrinkle GetFacelineWrinkle() const; | ||
| 30 | FacelineMake GetFacelineMake() const; | ||
| 31 | HairType GetHairType() const; | ||
| 32 | CommonColor GetHairColor() const; | ||
| 33 | HairFlip GetHairFlip() const; | ||
| 34 | EyeType GetEyeType() const; | ||
| 35 | CommonColor GetEyeColor() const; | ||
| 36 | u8 GetEyeScale() const; | ||
| 37 | u8 GetEyeAspect() const; | ||
| 38 | u8 GetEyeRotate() const; | ||
| 39 | u8 GetEyeX() const; | ||
| 40 | u8 GetEyeY() const; | ||
| 41 | EyebrowType GetEyebrowType() const; | ||
| 42 | CommonColor GetEyebrowColor() const; | ||
| 43 | u8 GetEyebrowScale() const; | ||
| 44 | u8 GetEyebrowAspect() const; | ||
| 45 | u8 GetEyebrowRotate() const; | ||
| 46 | u8 GetEyebrowX() const; | ||
| 47 | u8 GetEyebrowY() const; | ||
| 48 | NoseType GetNoseType() const; | ||
| 49 | u8 GetNoseScale() const; | ||
| 50 | u8 GetNoseY() const; | ||
| 51 | MouthType GetMouthType() const; | ||
| 52 | CommonColor GetMouthColor() const; | ||
| 53 | u8 GetMouthScale() const; | ||
| 54 | u8 GetMouthAspect() const; | ||
| 55 | u8 GetMouthY() const; | ||
| 56 | CommonColor GetBeardColor() const; | ||
| 57 | BeardType GetBeardType() const; | ||
| 58 | MustacheType GetMustacheType() const; | ||
| 59 | u8 GetMustacheScale() const; | ||
| 60 | u8 GetMustacheY() const; | ||
| 61 | GlassType GetGlassType() const; | ||
| 62 | CommonColor GetGlassColor() const; | ||
| 63 | u8 GetGlassScale() const; | ||
| 64 | u8 GetGlassY() const; | ||
| 65 | MoleType GetMoleType() const; | ||
| 66 | u8 GetMoleScale() const; | ||
| 67 | u8 GetMoleX() const; | ||
| 68 | u8 GetMoleY() const; | ||
| 69 | |||
| 70 | bool operator==(const CharInfo& info); | ||
| 71 | |||
| 72 | private: | ||
| 73 | Common::UUID create_id; | ||
| 74 | Nickname name; | ||
| 75 | u16 null_terminator; | ||
| 76 | FontRegion font_region; | ||
| 77 | FavoriteColor favorite_color; | ||
| 78 | Gender gender; | ||
| 79 | u8 height; | ||
| 80 | u8 build; | ||
| 81 | u8 type; | ||
| 82 | u8 region_move; | ||
| 83 | FacelineType faceline_type; | ||
| 84 | FacelineColor faceline_color; | ||
| 85 | FacelineWrinkle faceline_wrinkle; | ||
| 86 | FacelineMake faceline_make; | ||
| 87 | HairType hair_type; | ||
| 88 | CommonColor hair_color; | ||
| 89 | HairFlip hair_flip; | ||
| 90 | EyeType eye_type; | ||
| 91 | CommonColor eye_color; | ||
| 92 | u8 eye_scale; | ||
| 93 | u8 eye_aspect; | ||
| 94 | u8 eye_rotate; | ||
| 95 | u8 eye_x; | ||
| 96 | u8 eye_y; | ||
| 97 | EyebrowType eyebrow_type; | ||
| 98 | CommonColor eyebrow_color; | ||
| 99 | u8 eyebrow_scale; | ||
| 100 | u8 eyebrow_aspect; | ||
| 101 | u8 eyebrow_rotate; | ||
| 102 | u8 eyebrow_x; | ||
| 103 | u8 eyebrow_y; | ||
| 104 | NoseType nose_type; | ||
| 105 | u8 nose_scale; | ||
| 106 | u8 nose_y; | ||
| 107 | MouthType mouth_type; | ||
| 108 | CommonColor mouth_color; | ||
| 109 | u8 mouth_scale; | ||
| 110 | u8 mouth_aspect; | ||
| 111 | u8 mouth_y; | ||
| 112 | CommonColor beard_color; | ||
| 113 | BeardType beard_type; | ||
| 114 | MustacheType mustache_type; | ||
| 115 | u8 mustache_scale; | ||
| 116 | u8 mustache_y; | ||
| 117 | GlassType glass_type; | ||
| 118 | CommonColor glass_color; | ||
| 119 | u8 glass_scale; | ||
| 120 | u8 glass_y; | ||
| 121 | MoleType mole_type; | ||
| 122 | u8 mole_scale; | ||
| 123 | u8 mole_x; | ||
| 124 | u8 mole_y; | ||
| 125 | u8 padding; | ||
| 126 | }; | ||
| 127 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); | ||
| 128 | static_assert(std::has_unique_object_representations_v<CharInfo>, | ||
| 129 | "All bits of CharInfo must contribute to its value."); | ||
| 130 | |||
| 131 | struct CharInfoElement { | ||
| 132 | CharInfo char_info{}; | ||
| 133 | Source source{}; | ||
| 134 | }; | ||
| 135 | static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size."); | ||
| 136 | |||
| 137 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp new file mode 100644 index 000000000..659288b51 --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.cpp | |||
| @@ -0,0 +1,601 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/assert.h" | ||
| 5 | #include "core/hle/service/mii/mii_util.h" | ||
| 6 | #include "core/hle/service/mii/types/core_data.h" | ||
| 7 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | void CoreData::SetDefault() { | ||
| 12 | data = {}; | ||
| 13 | name = GetDefaultNickname(); | ||
| 14 | } | ||
| 15 | |||
| 16 | void CoreData::BuildRandom(Age age, Gender gender, Race race) { | ||
| 17 | if (gender == Gender::All) { | ||
| 18 | gender = MiiUtil::GetRandomValue(Gender::Max); | ||
| 19 | } | ||
| 20 | |||
| 21 | if (age == Age::All) { | ||
| 22 | const auto random{MiiUtil::GetRandomValue<int>(10)}; | ||
| 23 | if (random >= 8) { | ||
| 24 | age = Age::Old; | ||
| 25 | } else if (random >= 4) { | ||
| 26 | age = Age::Normal; | ||
| 27 | } else { | ||
| 28 | age = Age::Young; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | if (race == Race::All) { | ||
| 33 | const auto random{MiiUtil::GetRandomValue<int>(10)}; | ||
| 34 | if (random >= 8) { | ||
| 35 | race = Race::Black; | ||
| 36 | } else if (random >= 4) { | ||
| 37 | race = Race::White; | ||
| 38 | } else { | ||
| 39 | race = Race::Asian; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | SetGender(gender); | ||
| 44 | SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max)); | ||
| 45 | SetRegionMove(0); | ||
| 46 | SetFontRegion(FontRegion::Standard); | ||
| 47 | SetType(0); | ||
| 48 | SetHeight(64); | ||
| 49 | SetBuild(64); | ||
| 50 | |||
| 51 | u32 axis_y{}; | ||
| 52 | if (gender == Gender::Female && age == Age::Young) { | ||
| 53 | axis_y = MiiUtil::GetRandomValue<u32>(3); | ||
| 54 | } | ||
| 55 | |||
| 56 | const std::size_t index{3 * static_cast<std::size_t>(age) + | ||
| 57 | 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; | ||
| 58 | |||
| 59 | const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)}; | ||
| 60 | const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at( | ||
| 61 | 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; | ||
| 62 | const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; | ||
| 63 | const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; | ||
| 64 | const auto& hair_type_info{RawData::RandomMiiHairType.at(index)}; | ||
| 65 | const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + | ||
| 66 | static_cast<std::size_t>(age))}; | ||
| 67 | const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)}; | ||
| 68 | const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; | ||
| 69 | const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; | ||
| 70 | const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)}; | ||
| 71 | const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)}; | ||
| 72 | const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; | ||
| 73 | |||
| 74 | data.faceline_type.Assign( | ||
| 75 | faceline_type_info | ||
| 76 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]); | ||
| 77 | data.faceline_color.Assign( | ||
| 78 | faceline_color_info | ||
| 79 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]); | ||
| 80 | data.faceline_wrinkle.Assign( | ||
| 81 | faceline_wrinkle_info | ||
| 82 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); | ||
| 83 | data.faceline_makeup.Assign( | ||
| 84 | faceline_makeup_info | ||
| 85 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); | ||
| 86 | |||
| 87 | data.hair_type.Assign( | ||
| 88 | hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]); | ||
| 89 | SetHairColor(RawData::GetHairColorFromVer3( | ||
| 90 | hair_color_info | ||
| 91 | .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)])); | ||
| 92 | SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max)); | ||
| 93 | |||
| 94 | data.eye_type.Assign( | ||
| 95 | eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]); | ||
| 96 | |||
| 97 | const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; | ||
| 98 | const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; | ||
| 99 | const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; | ||
| 100 | const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]}; | ||
| 101 | |||
| 102 | SetEyeColor(RawData::GetEyeColorFromVer3( | ||
| 103 | eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)])); | ||
| 104 | SetEyeScale(4); | ||
| 105 | SetEyeAspect(3); | ||
| 106 | SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate)); | ||
| 107 | SetEyeX(2); | ||
| 108 | SetEyeY(static_cast<u8>(axis_y + 12)); | ||
| 109 | |||
| 110 | data.eyebrow_type.Assign( | ||
| 111 | eyebrow_type_info | ||
| 112 | .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | ||
| 113 | |||
| 114 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | ||
| 115 | const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | ||
| 116 | const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; | ||
| 117 | const auto eyebrow_rotate{ | ||
| 118 | 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; | ||
| 119 | |||
| 120 | SetEyebrowColor(GetHairColor()); | ||
| 121 | SetEyebrowScale(4); | ||
| 122 | SetEyebrowAspect(3); | ||
| 123 | SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate)); | ||
| 124 | SetEyebrowX(2); | ||
| 125 | SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y)); | ||
| 126 | |||
| 127 | data.nose_type.Assign( | ||
| 128 | nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]); | ||
| 129 | SetNoseScale(gender == Gender::Female ? 3 : 4); | ||
| 130 | SetNoseY(static_cast<u8>(axis_y + 9)); | ||
| 131 | |||
| 132 | const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0}; | ||
| 133 | |||
| 134 | data.mouth_type.Assign( | ||
| 135 | mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]); | ||
| 136 | SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color)); | ||
| 137 | SetMouthScale(4); | ||
| 138 | SetMouthAspect(3); | ||
| 139 | SetMouthY(static_cast<u8>(axis_y + 13)); | ||
| 140 | |||
| 141 | SetBeardColor(GetHairColor()); | ||
| 142 | SetMustacheScale(4); | ||
| 143 | |||
| 144 | if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) { | ||
| 145 | const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)}; | ||
| 146 | |||
| 147 | auto beard_type{BeardType::None}; | ||
| 148 | auto mustache_type{MustacheType::None}; | ||
| 149 | |||
| 150 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == | ||
| 151 | BeardAndMustacheFlag::Beard) { | ||
| 152 | beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max); | ||
| 153 | } | ||
| 154 | |||
| 155 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == | ||
| 156 | BeardAndMustacheFlag::Mustache) { | ||
| 157 | mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max); | ||
| 158 | } | ||
| 159 | |||
| 160 | SetMustacheType(mustache_type); | ||
| 161 | SetBeardType(beard_type); | ||
| 162 | SetMustacheY(10); | ||
| 163 | } else { | ||
| 164 | SetMustacheType(MustacheType::None); | ||
| 165 | SetBeardType(BeardType::None); | ||
| 166 | SetMustacheY(static_cast<u8>(axis_y + 10)); | ||
| 167 | } | ||
| 168 | |||
| 169 | const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)}; | ||
| 170 | u8 glasses_type{}; | ||
| 171 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { | ||
| 172 | if (++glasses_type >= glasses_type_info.values_count) { | ||
| 173 | ASSERT(false); | ||
| 174 | break; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | SetGlassType(static_cast<GlassType>(glasses_type)); | ||
| 179 | SetGlassColor(RawData::GetGlassColorFromVer3(0)); | ||
| 180 | SetGlassScale(4); | ||
| 181 | |||
| 182 | SetMoleType(MoleType::None); | ||
| 183 | SetMoleScale(4); | ||
| 184 | SetMoleX(2); | ||
| 185 | SetMoleY(20); | ||
| 186 | } | ||
| 187 | |||
| 188 | u32 CoreData::IsValid() const { | ||
| 189 | // TODO: Complete this | ||
| 190 | return 0; | ||
| 191 | } | ||
| 192 | |||
| 193 | void CoreData::SetFontRegion(FontRegion value) { | ||
| 194 | data.font_region.Assign(static_cast<u32>(value)); | ||
| 195 | } | ||
| 196 | |||
| 197 | void CoreData::SetFavoriteColor(FavoriteColor value) { | ||
| 198 | data.favorite_color.Assign(static_cast<u32>(value)); | ||
| 199 | } | ||
| 200 | |||
| 201 | void CoreData::SetGender(Gender value) { | ||
| 202 | data.gender.Assign(static_cast<u32>(value)); | ||
| 203 | } | ||
| 204 | |||
| 205 | void CoreData::SetHeight(u8 value) { | ||
| 206 | data.height.Assign(value); | ||
| 207 | } | ||
| 208 | |||
| 209 | void CoreData::SetBuild(u8 value) { | ||
| 210 | data.build.Assign(value); | ||
| 211 | } | ||
| 212 | |||
| 213 | void CoreData::SetType(u8 value) { | ||
| 214 | data.type.Assign(value); | ||
| 215 | } | ||
| 216 | |||
| 217 | void CoreData::SetRegionMove(u8 value) { | ||
| 218 | data.region_move.Assign(value); | ||
| 219 | } | ||
| 220 | |||
| 221 | void CoreData::SetFacelineType(FacelineType value) { | ||
| 222 | data.faceline_type.Assign(static_cast<u32>(value)); | ||
| 223 | } | ||
| 224 | |||
| 225 | void CoreData::SetFacelineColor(FacelineColor value) { | ||
| 226 | data.faceline_color.Assign(static_cast<u32>(value)); | ||
| 227 | } | ||
| 228 | |||
| 229 | void CoreData::SetFacelineWrinkle(FacelineWrinkle value) { | ||
| 230 | data.faceline_wrinkle.Assign(static_cast<u32>(value)); | ||
| 231 | } | ||
| 232 | |||
| 233 | void CoreData::SetFacelineMake(FacelineMake value) { | ||
| 234 | data.faceline_makeup.Assign(static_cast<u32>(value)); | ||
| 235 | } | ||
| 236 | |||
| 237 | void CoreData::SetHairType(HairType value) { | ||
| 238 | data.hair_type.Assign(static_cast<u32>(value)); | ||
| 239 | } | ||
| 240 | |||
| 241 | void CoreData::SetHairColor(CommonColor value) { | ||
| 242 | data.hair_color.Assign(static_cast<u32>(value)); | ||
| 243 | } | ||
| 244 | |||
| 245 | void CoreData::SetHairFlip(HairFlip value) { | ||
| 246 | data.hair_flip.Assign(static_cast<u32>(value)); | ||
| 247 | } | ||
| 248 | |||
| 249 | void CoreData::SetEyeType(EyeType value) { | ||
| 250 | data.eye_type.Assign(static_cast<u32>(value)); | ||
| 251 | } | ||
| 252 | |||
| 253 | void CoreData::SetEyeColor(CommonColor value) { | ||
| 254 | data.eye_color.Assign(static_cast<u32>(value)); | ||
| 255 | } | ||
| 256 | |||
| 257 | void CoreData::SetEyeScale(u8 value) { | ||
| 258 | data.eye_scale.Assign(value); | ||
| 259 | } | ||
| 260 | |||
| 261 | void CoreData::SetEyeAspect(u8 value) { | ||
| 262 | data.eye_aspect.Assign(value); | ||
| 263 | } | ||
| 264 | |||
| 265 | void CoreData::SetEyeRotate(u8 value) { | ||
| 266 | data.eye_rotate.Assign(value); | ||
| 267 | } | ||
| 268 | |||
| 269 | void CoreData::SetEyeX(u8 value) { | ||
| 270 | data.eye_x.Assign(value); | ||
| 271 | } | ||
| 272 | |||
| 273 | void CoreData::SetEyeY(u8 value) { | ||
| 274 | data.eye_y.Assign(value); | ||
| 275 | } | ||
| 276 | |||
| 277 | void CoreData::SetEyebrowType(EyebrowType value) { | ||
| 278 | data.eyebrow_type.Assign(static_cast<u32>(value)); | ||
| 279 | } | ||
| 280 | |||
| 281 | void CoreData::SetEyebrowColor(CommonColor value) { | ||
| 282 | data.eyebrow_color.Assign(static_cast<u32>(value)); | ||
| 283 | } | ||
| 284 | |||
| 285 | void CoreData::SetEyebrowScale(u8 value) { | ||
| 286 | data.eyebrow_scale.Assign(value); | ||
| 287 | } | ||
| 288 | |||
| 289 | void CoreData::SetEyebrowAspect(u8 value) { | ||
| 290 | data.eyebrow_aspect.Assign(value); | ||
| 291 | } | ||
| 292 | |||
| 293 | void CoreData::SetEyebrowRotate(u8 value) { | ||
| 294 | data.eyebrow_rotate.Assign(value); | ||
| 295 | } | ||
| 296 | |||
| 297 | void CoreData::SetEyebrowX(u8 value) { | ||
| 298 | data.eyebrow_x.Assign(value); | ||
| 299 | } | ||
| 300 | |||
| 301 | void CoreData::SetEyebrowY(u8 value) { | ||
| 302 | data.eyebrow_y.Assign(value); | ||
| 303 | } | ||
| 304 | |||
| 305 | void CoreData::SetNoseType(NoseType value) { | ||
| 306 | data.nose_type.Assign(static_cast<u32>(value)); | ||
| 307 | } | ||
| 308 | |||
| 309 | void CoreData::SetNoseScale(u8 value) { | ||
| 310 | data.nose_scale.Assign(value); | ||
| 311 | } | ||
| 312 | |||
| 313 | void CoreData::SetNoseY(u8 value) { | ||
| 314 | data.nose_y.Assign(value); | ||
| 315 | } | ||
| 316 | |||
| 317 | void CoreData::SetMouthType(u8 value) { | ||
| 318 | data.mouth_type.Assign(value); | ||
| 319 | } | ||
| 320 | |||
| 321 | void CoreData::SetMouthColor(CommonColor value) { | ||
| 322 | data.mouth_color.Assign(static_cast<u32>(value)); | ||
| 323 | } | ||
| 324 | |||
| 325 | void CoreData::SetMouthScale(u8 value) { | ||
| 326 | data.mouth_scale.Assign(value); | ||
| 327 | } | ||
| 328 | |||
| 329 | void CoreData::SetMouthAspect(u8 value) { | ||
| 330 | data.mouth_aspect.Assign(value); | ||
| 331 | } | ||
| 332 | |||
| 333 | void CoreData::SetMouthY(u8 value) { | ||
| 334 | data.mouth_y.Assign(value); | ||
| 335 | } | ||
| 336 | |||
| 337 | void CoreData::SetBeardColor(CommonColor value) { | ||
| 338 | data.beard_color.Assign(static_cast<u32>(value)); | ||
| 339 | } | ||
| 340 | |||
| 341 | void CoreData::SetBeardType(BeardType value) { | ||
| 342 | data.beard_type.Assign(static_cast<u32>(value)); | ||
| 343 | } | ||
| 344 | |||
| 345 | void CoreData::SetMustacheType(MustacheType value) { | ||
| 346 | data.mustache_type.Assign(static_cast<u32>(value)); | ||
| 347 | } | ||
| 348 | |||
| 349 | void CoreData::SetMustacheScale(u8 value) { | ||
| 350 | data.mustache_scale.Assign(value); | ||
| 351 | } | ||
| 352 | |||
| 353 | void CoreData::SetMustacheY(u8 value) { | ||
| 354 | data.mustache_y.Assign(value); | ||
| 355 | } | ||
| 356 | |||
| 357 | void CoreData::SetGlassType(GlassType value) { | ||
| 358 | data.glasses_type.Assign(static_cast<u32>(value)); | ||
| 359 | } | ||
| 360 | |||
| 361 | void CoreData::SetGlassColor(CommonColor value) { | ||
| 362 | data.glasses_color.Assign(static_cast<u32>(value)); | ||
| 363 | } | ||
| 364 | |||
| 365 | void CoreData::SetGlassScale(u8 value) { | ||
| 366 | data.glasses_scale.Assign(value); | ||
| 367 | } | ||
| 368 | |||
| 369 | void CoreData::SetGlassY(u8 value) { | ||
| 370 | data.glasses_y.Assign(value); | ||
| 371 | } | ||
| 372 | |||
| 373 | void CoreData::SetMoleType(MoleType value) { | ||
| 374 | data.mole_type.Assign(static_cast<u32>(value)); | ||
| 375 | } | ||
| 376 | |||
| 377 | void CoreData::SetMoleScale(u8 value) { | ||
| 378 | data.mole_scale.Assign(value); | ||
| 379 | } | ||
| 380 | |||
| 381 | void CoreData::SetMoleX(u8 value) { | ||
| 382 | data.mole_x.Assign(value); | ||
| 383 | } | ||
| 384 | |||
| 385 | void CoreData::SetMoleY(u8 value) { | ||
| 386 | data.mole_y.Assign(value); | ||
| 387 | } | ||
| 388 | |||
| 389 | void CoreData::SetNickname(Nickname nickname) { | ||
| 390 | name = nickname; | ||
| 391 | } | ||
| 392 | |||
| 393 | FontRegion CoreData::GetFontRegion() const { | ||
| 394 | return static_cast<FontRegion>(data.font_region.Value()); | ||
| 395 | } | ||
| 396 | |||
| 397 | FavoriteColor CoreData::GetFavoriteColor() const { | ||
| 398 | return static_cast<FavoriteColor>(data.favorite_color.Value()); | ||
| 399 | } | ||
| 400 | |||
| 401 | Gender CoreData::GetGender() const { | ||
| 402 | return static_cast<Gender>(data.gender.Value()); | ||
| 403 | } | ||
| 404 | |||
| 405 | u8 CoreData::GetHeight() const { | ||
| 406 | return static_cast<u8>(data.height.Value()); | ||
| 407 | } | ||
| 408 | |||
| 409 | u8 CoreData::GetBuild() const { | ||
| 410 | return static_cast<u8>(data.build.Value()); | ||
| 411 | } | ||
| 412 | |||
| 413 | u8 CoreData::GetType() const { | ||
| 414 | return static_cast<u8>(data.type.Value()); | ||
| 415 | } | ||
| 416 | |||
| 417 | u8 CoreData::GetRegionMove() const { | ||
| 418 | return static_cast<u8>(data.region_move.Value()); | ||
| 419 | } | ||
| 420 | |||
| 421 | FacelineType CoreData::GetFacelineType() const { | ||
| 422 | return static_cast<FacelineType>(data.faceline_type.Value()); | ||
| 423 | } | ||
| 424 | |||
| 425 | FacelineColor CoreData::GetFacelineColor() const { | ||
| 426 | return static_cast<FacelineColor>(data.faceline_color.Value()); | ||
| 427 | } | ||
| 428 | |||
| 429 | FacelineWrinkle CoreData::GetFacelineWrinkle() const { | ||
| 430 | return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value()); | ||
| 431 | } | ||
| 432 | |||
| 433 | FacelineMake CoreData::GetFacelineMake() const { | ||
| 434 | return static_cast<FacelineMake>(data.faceline_makeup.Value()); | ||
| 435 | } | ||
| 436 | |||
| 437 | HairType CoreData::GetHairType() const { | ||
| 438 | return static_cast<HairType>(data.hair_type.Value()); | ||
| 439 | } | ||
| 440 | |||
| 441 | CommonColor CoreData::GetHairColor() const { | ||
| 442 | return static_cast<CommonColor>(data.hair_color.Value()); | ||
| 443 | } | ||
| 444 | |||
| 445 | HairFlip CoreData::GetHairFlip() const { | ||
| 446 | return static_cast<HairFlip>(data.hair_flip.Value()); | ||
| 447 | } | ||
| 448 | |||
| 449 | EyeType CoreData::GetEyeType() const { | ||
| 450 | return static_cast<EyeType>(data.eye_type.Value()); | ||
| 451 | } | ||
| 452 | |||
| 453 | CommonColor CoreData::GetEyeColor() const { | ||
| 454 | return static_cast<CommonColor>(data.eye_color.Value()); | ||
| 455 | } | ||
| 456 | |||
| 457 | u8 CoreData::GetEyeScale() const { | ||
| 458 | return static_cast<u8>(data.eye_scale.Value()); | ||
| 459 | } | ||
| 460 | |||
| 461 | u8 CoreData::GetEyeAspect() const { | ||
| 462 | return static_cast<u8>(data.eye_aspect.Value()); | ||
| 463 | } | ||
| 464 | |||
| 465 | u8 CoreData::GetEyeRotate() const { | ||
| 466 | return static_cast<u8>(data.eye_rotate.Value()); | ||
| 467 | } | ||
| 468 | |||
| 469 | u8 CoreData::GetEyeX() const { | ||
| 470 | return static_cast<u8>(data.eye_x.Value()); | ||
| 471 | } | ||
| 472 | |||
| 473 | u8 CoreData::GetEyeY() const { | ||
| 474 | return static_cast<u8>(data.eye_y.Value()); | ||
| 475 | } | ||
| 476 | |||
| 477 | EyebrowType CoreData::GetEyebrowType() const { | ||
| 478 | return static_cast<EyebrowType>(data.eyebrow_type.Value()); | ||
| 479 | } | ||
| 480 | |||
| 481 | CommonColor CoreData::GetEyebrowColor() const { | ||
| 482 | return static_cast<CommonColor>(data.eyebrow_color.Value()); | ||
| 483 | } | ||
| 484 | |||
| 485 | u8 CoreData::GetEyebrowScale() const { | ||
| 486 | return static_cast<u8>(data.eyebrow_scale.Value()); | ||
| 487 | } | ||
| 488 | |||
| 489 | u8 CoreData::GetEyebrowAspect() const { | ||
| 490 | return static_cast<u8>(data.eyebrow_aspect.Value()); | ||
| 491 | } | ||
| 492 | |||
| 493 | u8 CoreData::GetEyebrowRotate() const { | ||
| 494 | return static_cast<u8>(data.eyebrow_rotate.Value()); | ||
| 495 | } | ||
| 496 | |||
| 497 | u8 CoreData::GetEyebrowX() const { | ||
| 498 | return static_cast<u8>(data.eyebrow_x.Value()); | ||
| 499 | } | ||
| 500 | |||
| 501 | u8 CoreData::GetEyebrowY() const { | ||
| 502 | return static_cast<u8>(data.eyebrow_y.Value()); | ||
| 503 | } | ||
| 504 | |||
| 505 | NoseType CoreData::GetNoseType() const { | ||
| 506 | return static_cast<NoseType>(data.nose_type.Value()); | ||
| 507 | } | ||
| 508 | |||
| 509 | u8 CoreData::GetNoseScale() const { | ||
| 510 | return static_cast<u8>(data.nose_scale.Value()); | ||
| 511 | } | ||
| 512 | |||
| 513 | u8 CoreData::GetNoseY() const { | ||
| 514 | return static_cast<u8>(data.nose_y.Value()); | ||
| 515 | } | ||
| 516 | |||
| 517 | MouthType CoreData::GetMouthType() const { | ||
| 518 | return static_cast<MouthType>(data.mouth_type.Value()); | ||
| 519 | } | ||
| 520 | |||
| 521 | CommonColor CoreData::GetMouthColor() const { | ||
| 522 | return static_cast<CommonColor>(data.mouth_color.Value()); | ||
| 523 | } | ||
| 524 | |||
| 525 | u8 CoreData::GetMouthScale() const { | ||
| 526 | return static_cast<u8>(data.mouth_scale.Value()); | ||
| 527 | } | ||
| 528 | |||
| 529 | u8 CoreData::GetMouthAspect() const { | ||
| 530 | return static_cast<u8>(data.mouth_aspect.Value()); | ||
| 531 | } | ||
| 532 | |||
| 533 | u8 CoreData::GetMouthY() const { | ||
| 534 | return static_cast<u8>(data.mouth_y.Value()); | ||
| 535 | } | ||
| 536 | |||
| 537 | CommonColor CoreData::GetBeardColor() const { | ||
| 538 | return static_cast<CommonColor>(data.beard_color.Value()); | ||
| 539 | } | ||
| 540 | |||
| 541 | BeardType CoreData::GetBeardType() const { | ||
| 542 | return static_cast<BeardType>(data.beard_type.Value()); | ||
| 543 | } | ||
| 544 | |||
| 545 | MustacheType CoreData::GetMustacheType() const { | ||
| 546 | return static_cast<MustacheType>(data.mustache_type.Value()); | ||
| 547 | } | ||
| 548 | |||
| 549 | u8 CoreData::GetMustacheScale() const { | ||
| 550 | return static_cast<u8>(data.mustache_scale.Value()); | ||
| 551 | } | ||
| 552 | |||
| 553 | u8 CoreData::GetMustacheY() const { | ||
| 554 | return static_cast<u8>(data.mustache_y.Value()); | ||
| 555 | } | ||
| 556 | |||
| 557 | GlassType CoreData::GetGlassType() const { | ||
| 558 | return static_cast<GlassType>(data.glasses_type.Value()); | ||
| 559 | } | ||
| 560 | |||
| 561 | CommonColor CoreData::GetGlassColor() const { | ||
| 562 | return static_cast<CommonColor>(data.glasses_color.Value()); | ||
| 563 | } | ||
| 564 | |||
| 565 | u8 CoreData::GetGlassScale() const { | ||
| 566 | return static_cast<u8>(data.glasses_scale.Value()); | ||
| 567 | } | ||
| 568 | |||
| 569 | u8 CoreData::GetGlassY() const { | ||
| 570 | return static_cast<u8>(data.glasses_y.Value()); | ||
| 571 | } | ||
| 572 | |||
| 573 | MoleType CoreData::GetMoleType() const { | ||
| 574 | return static_cast<MoleType>(data.mole_type.Value()); | ||
| 575 | } | ||
| 576 | |||
| 577 | u8 CoreData::GetMoleScale() const { | ||
| 578 | return static_cast<u8>(data.mole_scale.Value()); | ||
| 579 | } | ||
| 580 | |||
| 581 | u8 CoreData::GetMoleX() const { | ||
| 582 | return static_cast<u8>(data.mole_x.Value()); | ||
| 583 | } | ||
| 584 | |||
| 585 | u8 CoreData::GetMoleY() const { | ||
| 586 | return static_cast<u8>(data.mole_y.Value()); | ||
| 587 | } | ||
| 588 | |||
| 589 | Nickname CoreData::GetNickname() const { | ||
| 590 | return name; | ||
| 591 | } | ||
| 592 | |||
| 593 | Nickname CoreData::GetDefaultNickname() const { | ||
| 594 | return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; | ||
| 595 | } | ||
| 596 | |||
| 597 | Nickname CoreData::GetInvalidNickname() const { | ||
| 598 | return {u'?', u'?', u'?'}; | ||
| 599 | } | ||
| 600 | |||
| 601 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h new file mode 100644 index 000000000..cebcd2ee4 --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.h | |||
| @@ -0,0 +1,216 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | struct StoreDataBitFields { | ||
| 11 | union { | ||
| 12 | u32 word_0{}; | ||
| 13 | |||
| 14 | BitField<0, 8, u32> hair_type; | ||
| 15 | BitField<8, 7, u32> height; | ||
| 16 | BitField<15, 1, u32> mole_type; | ||
| 17 | BitField<16, 7, u32> build; | ||
| 18 | BitField<23, 1, u32> hair_flip; | ||
| 19 | BitField<24, 7, u32> hair_color; | ||
| 20 | BitField<31, 1, u32> type; | ||
| 21 | }; | ||
| 22 | |||
| 23 | union { | ||
| 24 | u32 word_1{}; | ||
| 25 | |||
| 26 | BitField<0, 7, u32> eye_color; | ||
| 27 | BitField<7, 1, u32> gender; | ||
| 28 | BitField<8, 7, u32> eyebrow_color; | ||
| 29 | BitField<16, 7, u32> mouth_color; | ||
| 30 | BitField<24, 7, u32> beard_color; | ||
| 31 | }; | ||
| 32 | |||
| 33 | union { | ||
| 34 | u32 word_2{}; | ||
| 35 | |||
| 36 | BitField<0, 7, u32> glasses_color; | ||
| 37 | BitField<8, 6, u32> eye_type; | ||
| 38 | BitField<14, 2, u32> region_move; | ||
| 39 | BitField<16, 6, u32> mouth_type; | ||
| 40 | BitField<22, 2, u32> font_region; | ||
| 41 | BitField<24, 5, u32> eye_y; | ||
| 42 | BitField<29, 3, u32> glasses_scale; | ||
| 43 | }; | ||
| 44 | |||
| 45 | union { | ||
| 46 | u32 word_3{}; | ||
| 47 | |||
| 48 | BitField<0, 5, u32> eyebrow_type; | ||
| 49 | BitField<5, 3, u32> mustache_type; | ||
| 50 | BitField<8, 5, u32> nose_type; | ||
| 51 | BitField<13, 3, u32> beard_type; | ||
| 52 | BitField<16, 5, u32> nose_y; | ||
| 53 | BitField<21, 3, u32> mouth_aspect; | ||
| 54 | BitField<24, 5, u32> mouth_y; | ||
| 55 | BitField<29, 3, u32> eyebrow_aspect; | ||
| 56 | }; | ||
| 57 | |||
| 58 | union { | ||
| 59 | u32 word_4{}; | ||
| 60 | |||
| 61 | BitField<0, 5, u32> mustache_y; | ||
| 62 | BitField<5, 3, u32> eye_rotate; | ||
| 63 | BitField<8, 5, u32> glasses_y; | ||
| 64 | BitField<13, 3, u32> eye_aspect; | ||
| 65 | BitField<16, 5, u32> mole_x; | ||
| 66 | BitField<21, 3, u32> eye_scale; | ||
| 67 | BitField<24, 5, u32> mole_y; | ||
| 68 | }; | ||
| 69 | |||
| 70 | union { | ||
| 71 | u32 word_5{}; | ||
| 72 | |||
| 73 | BitField<0, 5, u32> glasses_type; | ||
| 74 | BitField<8, 4, u32> favorite_color; | ||
| 75 | BitField<12, 4, u32> faceline_type; | ||
| 76 | BitField<16, 4, u32> faceline_color; | ||
| 77 | BitField<20, 4, u32> faceline_wrinkle; | ||
| 78 | BitField<24, 4, u32> faceline_makeup; | ||
| 79 | BitField<28, 4, u32> eye_x; | ||
| 80 | }; | ||
| 81 | |||
| 82 | union { | ||
| 83 | u32 word_6{}; | ||
| 84 | |||
| 85 | BitField<0, 4, u32> eyebrow_scale; | ||
| 86 | BitField<4, 4, u32> eyebrow_rotate; | ||
| 87 | BitField<8, 4, u32> eyebrow_x; | ||
| 88 | BitField<12, 4, u32> eyebrow_y; | ||
| 89 | BitField<16, 4, u32> nose_scale; | ||
| 90 | BitField<20, 4, u32> mouth_scale; | ||
| 91 | BitField<24, 4, u32> mustache_scale; | ||
| 92 | BitField<28, 4, u32> mole_scale; | ||
| 93 | }; | ||
| 94 | }; | ||
| 95 | static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size."); | ||
| 96 | static_assert(std::is_trivially_copyable_v<StoreDataBitFields>, | ||
| 97 | "StoreDataBitFields is not trivially copyable."); | ||
| 98 | |||
| 99 | class CoreData { | ||
| 100 | public: | ||
| 101 | void SetDefault(); | ||
| 102 | void BuildRandom(Age age, Gender gender, Race race); | ||
| 103 | |||
| 104 | u32 IsValid() const; | ||
| 105 | |||
| 106 | void SetFontRegion(FontRegion value); | ||
| 107 | void SetFavoriteColor(FavoriteColor value); | ||
| 108 | void SetGender(Gender value); | ||
| 109 | void SetHeight(u8 value); | ||
| 110 | void SetBuild(u8 value); | ||
| 111 | void SetType(u8 value); | ||
| 112 | void SetRegionMove(u8 value); | ||
| 113 | void SetFacelineType(FacelineType value); | ||
| 114 | void SetFacelineColor(FacelineColor value); | ||
| 115 | void SetFacelineWrinkle(FacelineWrinkle value); | ||
| 116 | void SetFacelineMake(FacelineMake value); | ||
| 117 | void SetHairType(HairType value); | ||
| 118 | void SetHairColor(CommonColor value); | ||
| 119 | void SetHairFlip(HairFlip value); | ||
| 120 | void SetEyeType(EyeType value); | ||
| 121 | void SetEyeColor(CommonColor value); | ||
| 122 | void SetEyeScale(u8 value); | ||
| 123 | void SetEyeAspect(u8 value); | ||
| 124 | void SetEyeRotate(u8 value); | ||
| 125 | void SetEyeX(u8 value); | ||
| 126 | void SetEyeY(u8 value); | ||
| 127 | void SetEyebrowType(EyebrowType value); | ||
| 128 | void SetEyebrowColor(CommonColor value); | ||
| 129 | void SetEyebrowScale(u8 value); | ||
| 130 | void SetEyebrowAspect(u8 value); | ||
| 131 | void SetEyebrowRotate(u8 value); | ||
| 132 | void SetEyebrowX(u8 value); | ||
| 133 | void SetEyebrowY(u8 value); | ||
| 134 | void SetNoseType(NoseType value); | ||
| 135 | void SetNoseScale(u8 value); | ||
| 136 | void SetNoseY(u8 value); | ||
| 137 | void SetMouthType(u8 value); | ||
| 138 | void SetMouthColor(CommonColor value); | ||
| 139 | void SetMouthScale(u8 value); | ||
| 140 | void SetMouthAspect(u8 value); | ||
| 141 | void SetMouthY(u8 value); | ||
| 142 | void SetBeardColor(CommonColor value); | ||
| 143 | void SetBeardType(BeardType value); | ||
| 144 | void SetMustacheType(MustacheType value); | ||
| 145 | void SetMustacheScale(u8 value); | ||
| 146 | void SetMustacheY(u8 value); | ||
| 147 | void SetGlassType(GlassType value); | ||
| 148 | void SetGlassColor(CommonColor value); | ||
| 149 | void SetGlassScale(u8 value); | ||
| 150 | void SetGlassY(u8 value); | ||
| 151 | void SetMoleType(MoleType value); | ||
| 152 | void SetMoleScale(u8 value); | ||
| 153 | void SetMoleX(u8 value); | ||
| 154 | void SetMoleY(u8 value); | ||
| 155 | void SetNickname(Nickname nickname); | ||
| 156 | |||
| 157 | FontRegion GetFontRegion() const; | ||
| 158 | FavoriteColor GetFavoriteColor() const; | ||
| 159 | Gender GetGender() const; | ||
| 160 | u8 GetHeight() const; | ||
| 161 | u8 GetBuild() const; | ||
| 162 | u8 GetType() const; | ||
| 163 | u8 GetRegionMove() const; | ||
| 164 | FacelineType GetFacelineType() const; | ||
| 165 | FacelineColor GetFacelineColor() const; | ||
| 166 | FacelineWrinkle GetFacelineWrinkle() const; | ||
| 167 | FacelineMake GetFacelineMake() const; | ||
| 168 | HairType GetHairType() const; | ||
| 169 | CommonColor GetHairColor() const; | ||
| 170 | HairFlip GetHairFlip() const; | ||
| 171 | EyeType GetEyeType() const; | ||
| 172 | CommonColor GetEyeColor() const; | ||
| 173 | u8 GetEyeScale() const; | ||
| 174 | u8 GetEyeAspect() const; | ||
| 175 | u8 GetEyeRotate() const; | ||
| 176 | u8 GetEyeX() const; | ||
| 177 | u8 GetEyeY() const; | ||
| 178 | EyebrowType GetEyebrowType() const; | ||
| 179 | CommonColor GetEyebrowColor() const; | ||
| 180 | u8 GetEyebrowScale() const; | ||
| 181 | u8 GetEyebrowAspect() const; | ||
| 182 | u8 GetEyebrowRotate() const; | ||
| 183 | u8 GetEyebrowX() const; | ||
| 184 | u8 GetEyebrowY() const; | ||
| 185 | NoseType GetNoseType() const; | ||
| 186 | u8 GetNoseScale() const; | ||
| 187 | u8 GetNoseY() const; | ||
| 188 | MouthType GetMouthType() const; | ||
| 189 | CommonColor GetMouthColor() const; | ||
| 190 | u8 GetMouthScale() const; | ||
| 191 | u8 GetMouthAspect() const; | ||
| 192 | u8 GetMouthY() const; | ||
| 193 | CommonColor GetBeardColor() const; | ||
| 194 | BeardType GetBeardType() const; | ||
| 195 | MustacheType GetMustacheType() const; | ||
| 196 | u8 GetMustacheScale() const; | ||
| 197 | u8 GetMustacheY() const; | ||
| 198 | GlassType GetGlassType() const; | ||
| 199 | CommonColor GetGlassColor() const; | ||
| 200 | u8 GetGlassScale() const; | ||
| 201 | u8 GetGlassY() const; | ||
| 202 | MoleType GetMoleType() const; | ||
| 203 | u8 GetMoleScale() const; | ||
| 204 | u8 GetMoleX() const; | ||
| 205 | u8 GetMoleY() const; | ||
| 206 | Nickname GetNickname() const; | ||
| 207 | Nickname GetDefaultNickname() const; | ||
| 208 | Nickname GetInvalidNickname() const; | ||
| 209 | |||
| 210 | private: | ||
| 211 | StoreDataBitFields data{}; | ||
| 212 | Nickname name{}; | ||
| 213 | }; | ||
| 214 | static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); | ||
| 215 | |||
| 216 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp index 1442280c8..5143abcc8 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/types/raw_data.cpp | |||
| @@ -1,11 +1,88 @@ | |||
| 1 | // SPDX-FileCopyrightText: Ryujinx Team and Contributors | 1 | // SPDX-FileCopyrightText: Ryujinx Team and Contributors |
| 2 | // SPDX-License-Identifier: MIT | 2 | // SPDX-License-Identifier: MIT |
| 3 | 3 | ||
| 4 | #include "core/hle/service/mii/raw_data.h" | 4 | #include "core/hle/service/mii/types/raw_data.h" |
| 5 | 5 | ||
| 6 | namespace Service::Mii::RawData { | 6 | namespace Service::Mii::RawData { |
| 7 | 7 | ||
| 8 | const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | 8 | constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{ |
| 9 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, | ||
| 10 | }; | ||
| 11 | |||
| 12 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{ | ||
| 13 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, | ||
| 14 | 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 15 | 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, | ||
| 16 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, | ||
| 17 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, | ||
| 18 | 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, | ||
| 19 | }; | ||
| 20 | |||
| 21 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{ | ||
| 22 | 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, | ||
| 23 | 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 24 | 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, | ||
| 25 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 26 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, | ||
| 27 | 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, | ||
| 28 | }; | ||
| 29 | |||
| 30 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{ | ||
| 31 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 32 | 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 33 | 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 34 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 35 | 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, | ||
| 36 | 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, | ||
| 37 | }; | ||
| 38 | |||
| 39 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{ | ||
| 40 | 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, | ||
| 41 | 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 42 | 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, | ||
| 43 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, | ||
| 44 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, | ||
| 45 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 46 | }; | ||
| 47 | |||
| 48 | constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{ | ||
| 49 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, | ||
| 50 | 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, | ||
| 51 | }; | ||
| 52 | |||
| 53 | constexpr std::array<u8, 8> Ver3FacelineColorTable{ | ||
| 54 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, | ||
| 55 | }; | ||
| 56 | |||
| 57 | constexpr std::array<u8, 8> Ver3HairColorTable{ | ||
| 58 | 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, | ||
| 59 | }; | ||
| 60 | |||
| 61 | constexpr std::array<u8, 6> Ver3EyeColorTable{ | ||
| 62 | 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, | ||
| 63 | }; | ||
| 64 | |||
| 65 | constexpr std::array<u8, 5> Ver3MouthColorTable{ | ||
| 66 | 0x13, 0x14, 0x15, 0x16, 0x17, | ||
| 67 | }; | ||
| 68 | |||
| 69 | constexpr std::array<u8, 7> Ver3GlassColorTable{ | ||
| 70 | 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0, | ||
| 71 | }; | ||
| 72 | |||
| 73 | const std::array<u8, 62> EyeRotateLookup{ | ||
| 74 | 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, | ||
| 75 | 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||
| 76 | 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, | ||
| 77 | 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||
| 78 | }; | ||
| 79 | |||
| 80 | const std::array<u8, 24> EyebrowRotateLookup{ | ||
| 81 | 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08, | ||
| 82 | 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05, | ||
| 83 | }; | ||
| 84 | |||
| 85 | const std::array<Service::Mii::DefaultMii, 2> BaseMii{ | ||
| 9 | Service::Mii::DefaultMii{ | 86 | Service::Mii::DefaultMii{ |
| 10 | .face_type = 0, | 87 | .face_type = 0, |
| 11 | .face_color = 0, | 88 | .face_color = 0, |
| @@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 51 | .mole_y = 20, | 128 | .mole_y = 20, |
| 52 | .height = 64, | 129 | .height = 64, |
| 53 | .weight = 64, | 130 | .weight = 64, |
| 54 | .gender = Gender::Male, | 131 | .gender = 0, |
| 55 | .favorite_color = 0, | 132 | .favorite_color = 0, |
| 56 | .region = 0, | 133 | .region_move = 0, |
| 57 | .font_region = FontRegion::Standard, | 134 | .font_region = 0, |
| 58 | .type = 0, | 135 | .type = 0, |
| 136 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 59 | }, | 137 | }, |
| 60 | Service::Mii::DefaultMii{ | 138 | Service::Mii::DefaultMii{ |
| 61 | .face_type = 0, | 139 | .face_type = 0, |
| @@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 102 | .mole_y = 20, | 180 | .mole_y = 20, |
| 103 | .height = 64, | 181 | .height = 64, |
| 104 | .weight = 64, | 182 | .weight = 64, |
| 105 | .gender = Gender::Female, | 183 | .gender = 1, |
| 106 | .favorite_color = 0, | 184 | .favorite_color = 0, |
| 107 | .region = 0, | 185 | .region_move = 0, |
| 108 | .font_region = FontRegion::Standard, | 186 | .font_region = 0, |
| 109 | .type = 0, | 187 | .type = 0, |
| 188 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 110 | }, | 189 | }, |
| 190 | }; | ||
| 191 | |||
| 192 | const std::array<Service::Mii::DefaultMii, 6> DefaultMii{ | ||
| 111 | Service::Mii::DefaultMii{ | 193 | Service::Mii::DefaultMii{ |
| 112 | .face_type = 0, | 194 | .face_type = 0, |
| 113 | .face_color = 4, | 195 | .face_color = 4, |
| @@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 153 | .mole_y = 20, | 235 | .mole_y = 20, |
| 154 | .height = 64, | 236 | .height = 64, |
| 155 | .weight = 64, | 237 | .weight = 64, |
| 156 | .gender = Gender::Male, | 238 | .gender = 0, |
| 157 | .favorite_color = 4, | 239 | .favorite_color = 4, |
| 158 | .region = 0, | 240 | .region_move = 0, |
| 159 | .font_region = FontRegion::Standard, | 241 | .font_region = 0, |
| 160 | .type = 0, | 242 | .type = 0, |
| 243 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 161 | }, | 244 | }, |
| 162 | Service::Mii::DefaultMii{ | 245 | Service::Mii::DefaultMii{ |
| 163 | .face_type = 0, | 246 | .face_type = 0, |
| @@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 204 | .mole_y = 20, | 287 | .mole_y = 20, |
| 205 | .height = 64, | 288 | .height = 64, |
| 206 | .weight = 64, | 289 | .weight = 64, |
| 207 | .gender = Gender::Male, | 290 | .gender = 0, |
| 208 | .favorite_color = 5, | 291 | .favorite_color = 5, |
| 209 | .region = 0, | 292 | .region_move = 0, |
| 210 | .font_region = FontRegion::Standard, | 293 | .font_region = 0, |
| 211 | .type = 0, | 294 | .type = 0, |
| 295 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 212 | }, | 296 | }, |
| 213 | Service::Mii::DefaultMii{ | 297 | Service::Mii::DefaultMii{ |
| 214 | .face_type = 0, | 298 | .face_type = 0, |
| @@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 255 | .mole_y = 20, | 339 | .mole_y = 20, |
| 256 | .height = 64, | 340 | .height = 64, |
| 257 | .weight = 64, | 341 | .weight = 64, |
| 258 | .gender = Gender::Male, | 342 | .gender = 0, |
| 259 | .favorite_color = 0, | 343 | .favorite_color = 0, |
| 260 | .region = 0, | 344 | .region_move = 0, |
| 261 | .font_region = FontRegion::Standard, | 345 | .font_region = 0, |
| 262 | .type = 0, | 346 | .type = 0, |
| 347 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 263 | }, | 348 | }, |
| 264 | Service::Mii::DefaultMii{ | 349 | Service::Mii::DefaultMii{ |
| 265 | .face_type = 0, | 350 | .face_type = 0, |
| @@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 306 | .mole_y = 20, | 391 | .mole_y = 20, |
| 307 | .height = 64, | 392 | .height = 64, |
| 308 | .weight = 64, | 393 | .weight = 64, |
| 309 | .gender = Gender::Female, | 394 | .gender = 1, |
| 310 | .favorite_color = 2, | 395 | .favorite_color = 2, |
| 311 | .region = 0, | 396 | .region_move = 0, |
| 312 | .font_region = FontRegion::Standard, | 397 | .font_region = 0, |
| 313 | .type = 0, | 398 | .type = 0, |
| 399 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 314 | }, | 400 | }, |
| 315 | Service::Mii::DefaultMii{ | 401 | Service::Mii::DefaultMii{ |
| 316 | .face_type = 0, | 402 | .face_type = 0, |
| @@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 357 | .mole_y = 20, | 443 | .mole_y = 20, |
| 358 | .height = 64, | 444 | .height = 64, |
| 359 | .weight = 64, | 445 | .weight = 64, |
| 360 | .gender = Gender::Female, | 446 | .gender = 1, |
| 361 | .favorite_color = 6, | 447 | .favorite_color = 6, |
| 362 | .region = 0, | 448 | .region_move = 0, |
| 363 | .font_region = FontRegion::Standard, | 449 | .font_region = 0, |
| 364 | .type = 0, | 450 | .type = 0, |
| 451 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 365 | }, | 452 | }, |
| 366 | Service::Mii::DefaultMii{ | 453 | Service::Mii::DefaultMii{ |
| 367 | .face_type = 0, | 454 | .face_type = 0, |
| @@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 408 | .mole_y = 20, | 495 | .mole_y = 20, |
| 409 | .height = 64, | 496 | .height = 64, |
| 410 | .weight = 64, | 497 | .weight = 64, |
| 411 | .gender = Gender::Female, | 498 | .gender = 1, |
| 412 | .favorite_color = 7, | 499 | .favorite_color = 7, |
| 413 | .region = 0, | 500 | .region_move = 0, |
| 414 | .font_region = FontRegion::Standard, | 501 | .font_region = 0, |
| 415 | .type = 0, | 502 | .type = 0, |
| 503 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 416 | }, | 504 | }, |
| 417 | 505 | ||
| 418 | }; | 506 | }; |
| 419 | 507 | ||
| 420 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ | 508 | const std::array<RandomMiiData4, 18> RandomMiiFaceline{ |
| 421 | Service::Mii::RandomMiiData4{ | 509 | RandomMiiData4{ |
| 422 | .gender = Gender::Male, | 510 | .gender = static_cast<u32>(Gender::Male), |
| 423 | .age = Age::Young, | 511 | .age = static_cast<u32>(Age::Young), |
| 424 | .race = Race::Black, | 512 | .race = static_cast<u32>(Race::Black), |
| 425 | .values_count = 10, | 513 | .values_count = 10, |
| 426 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 514 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 427 | }, | 515 | }, |
| 428 | Service::Mii::RandomMiiData4{ | 516 | RandomMiiData4{ |
| 429 | .gender = Gender::Male, | 517 | .gender = static_cast<u32>(Gender::Male), |
| 430 | .age = Age::Normal, | 518 | .age = static_cast<u32>(Age::Normal), |
| 431 | .race = Race::Black, | 519 | .race = static_cast<u32>(Race::Black), |
| 432 | .values_count = 10, | 520 | .values_count = 10, |
| 433 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 521 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 434 | }, | 522 | }, |
| 435 | Service::Mii::RandomMiiData4{ | 523 | RandomMiiData4{ |
| 436 | .gender = Gender::Male, | 524 | .gender = static_cast<u32>(Gender::Male), |
| 437 | .age = Age::Old, | 525 | .age = static_cast<u32>(Age::Old), |
| 438 | .race = Race::Black, | 526 | .race = static_cast<u32>(Race::Black), |
| 439 | .values_count = 10, | 527 | .values_count = 10, |
| 440 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 528 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 441 | }, | 529 | }, |
| 442 | Service::Mii::RandomMiiData4{ | 530 | RandomMiiData4{ |
| 443 | .gender = Gender::Male, | 531 | .gender = static_cast<u32>(Gender::Male), |
| 444 | .age = Age::Young, | 532 | .age = static_cast<u32>(Age::Young), |
| 445 | .race = Race::White, | 533 | .race = static_cast<u32>(Race::White), |
| 446 | .values_count = 12, | 534 | .values_count = 12, |
| 447 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 535 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 448 | }, | 536 | }, |
| 449 | Service::Mii::RandomMiiData4{ | 537 | RandomMiiData4{ |
| 450 | .gender = Gender::Male, | 538 | .gender = static_cast<u32>(Gender::Male), |
| 451 | .age = Age::Normal, | 539 | .age = static_cast<u32>(Age::Normal), |
| 452 | .race = Race::White, | 540 | .race = static_cast<u32>(Race::White), |
| 453 | .values_count = 13, | 541 | .values_count = 13, |
| 454 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, | 542 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, |
| 455 | }, | 543 | }, |
| 456 | Service::Mii::RandomMiiData4{ | 544 | RandomMiiData4{ |
| 457 | .gender = Gender::Male, | 545 | .gender = static_cast<u32>(Gender::Male), |
| 458 | .age = Age::Old, | 546 | .age = static_cast<u32>(Age::Old), |
| 459 | .race = Race::White, | 547 | .race = static_cast<u32>(Race::White), |
| 460 | .values_count = 12, | 548 | .values_count = 12, |
| 461 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 549 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 462 | }, | 550 | }, |
| 463 | Service::Mii::RandomMiiData4{ | 551 | RandomMiiData4{ |
| 464 | .gender = Gender::Male, | 552 | .gender = static_cast<u32>(Gender::Male), |
| 465 | .age = Age::Young, | 553 | .age = static_cast<u32>(Age::Young), |
| 466 | .race = Race::Asian, | 554 | .race = static_cast<u32>(Race::Asian), |
| 467 | .values_count = 12, | 555 | .values_count = 12, |
| 468 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 556 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 469 | }, | 557 | }, |
| 470 | Service::Mii::RandomMiiData4{ | 558 | RandomMiiData4{ |
| 471 | .gender = Gender::Male, | 559 | .gender = static_cast<u32>(Gender::Male), |
| 472 | .age = Age::Normal, | 560 | .age = static_cast<u32>(Age::Normal), |
| 473 | .race = Race::Asian, | 561 | .race = static_cast<u32>(Race::Asian), |
| 474 | .values_count = 13, | 562 | .values_count = 13, |
| 475 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, | 563 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, |
| 476 | }, | 564 | }, |
| 477 | Service::Mii::RandomMiiData4{ | 565 | RandomMiiData4{ |
| 478 | .gender = Gender::Male, | 566 | .gender = static_cast<u32>(Gender::Male), |
| 479 | .age = Age::Old, | 567 | .age = static_cast<u32>(Age::Old), |
| 480 | .race = Race::Asian, | 568 | .race = static_cast<u32>(Race::Asian), |
| 481 | .values_count = 12, | 569 | .values_count = 12, |
| 482 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 570 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 483 | }, | 571 | }, |
| 484 | Service::Mii::RandomMiiData4{ | 572 | RandomMiiData4{ |
| 485 | .gender = Gender::Female, | 573 | .gender = static_cast<u32>(Gender::Female), |
| 486 | .age = Age::Young, | 574 | .age = static_cast<u32>(Age::Young), |
| 487 | .race = Race::Black, | 575 | .race = static_cast<u32>(Race::Black), |
| 488 | .values_count = 10, | 576 | .values_count = 10, |
| 489 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 577 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 490 | }, | 578 | }, |
| 491 | Service::Mii::RandomMiiData4{ | 579 | RandomMiiData4{ |
| 492 | .gender = Gender::Female, | 580 | .gender = static_cast<u32>(Gender::Female), |
| 493 | .age = Age::Normal, | 581 | .age = static_cast<u32>(Age::Normal), |
| 494 | .race = Race::Black, | 582 | .race = static_cast<u32>(Race::Black), |
| 495 | .values_count = 10, | 583 | .values_count = 10, |
| 496 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 584 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 497 | }, | 585 | }, |
| 498 | Service::Mii::RandomMiiData4{ | 586 | RandomMiiData4{ |
| 499 | .gender = Gender::Female, | 587 | .gender = static_cast<u32>(Gender::Female), |
| 500 | .age = Age::Old, | 588 | .age = static_cast<u32>(Age::Old), |
| 501 | .race = Race::Black, | 589 | .race = static_cast<u32>(Race::Black), |
| 502 | .values_count = 10, | 590 | .values_count = 10, |
| 503 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 591 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 504 | }, | 592 | }, |
| 505 | Service::Mii::RandomMiiData4{ | 593 | RandomMiiData4{ |
| 506 | .gender = Gender::Female, | 594 | .gender = static_cast<u32>(Gender::Female), |
| 507 | .age = Age::Young, | 595 | .age = static_cast<u32>(Age::Young), |
| 508 | .race = Race::White, | 596 | .race = static_cast<u32>(Race::White), |
| 509 | .values_count = 12, | 597 | .values_count = 12, |
| 510 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 598 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 511 | }, | 599 | }, |
| 512 | Service::Mii::RandomMiiData4{ | 600 | RandomMiiData4{ |
| 513 | .gender = Gender::Female, | 601 | .gender = static_cast<u32>(Gender::Female), |
| 514 | .age = Age::Normal, | 602 | .age = static_cast<u32>(Age::Normal), |
| 515 | .race = Race::White, | 603 | .race = static_cast<u32>(Race::White), |
| 516 | .values_count = 12, | 604 | .values_count = 12, |
| 517 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 605 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 518 | }, | 606 | }, |
| 519 | Service::Mii::RandomMiiData4{ | 607 | RandomMiiData4{ |
| 520 | .gender = Gender::Female, | 608 | .gender = static_cast<u32>(Gender::Female), |
| 521 | .age = Age::Old, | 609 | .age = static_cast<u32>(Age::Old), |
| 522 | .race = Race::White, | 610 | .race = static_cast<u32>(Race::White), |
| 523 | .values_count = 12, | 611 | .values_count = 12, |
| 524 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 612 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 525 | }, | 613 | }, |
| 526 | Service::Mii::RandomMiiData4{ | 614 | RandomMiiData4{ |
| 527 | .gender = Gender::Female, | 615 | .gender = static_cast<u32>(Gender::Female), |
| 528 | .age = Age::Young, | 616 | .age = static_cast<u32>(Age::Young), |
| 529 | .race = Race::Asian, | 617 | .race = static_cast<u32>(Race::Asian), |
| 530 | .values_count = 12, | 618 | .values_count = 12, |
| 531 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 619 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 532 | }, | 620 | }, |
| 533 | Service::Mii::RandomMiiData4{ | 621 | RandomMiiData4{ |
| 534 | .gender = Gender::Female, | 622 | .gender = static_cast<u32>(Gender::Female), |
| 535 | .age = Age::Normal, | 623 | .age = static_cast<u32>(Age::Normal), |
| 536 | .race = Race::Asian, | 624 | .race = static_cast<u32>(Race::Asian), |
| 537 | .values_count = 12, | 625 | .values_count = 12, |
| 538 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 626 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 539 | }, | 627 | }, |
| 540 | Service::Mii::RandomMiiData4{ | 628 | RandomMiiData4{ |
| 541 | .gender = Gender::Female, | 629 | .gender = static_cast<u32>(Gender::Female), |
| 542 | .age = Age::Old, | 630 | .age = static_cast<u32>(Age::Old), |
| 543 | .race = Race::Asian, | 631 | .race = static_cast<u32>(Race::Asian), |
| 544 | .values_count = 12, | 632 | .values_count = 12, |
| 545 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 633 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 546 | }, | 634 | }, |
| 547 | }; | 635 | }; |
| 548 | 636 | ||
| 549 | const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ | 637 | const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{ |
| 550 | Service::Mii::RandomMiiData3{ | 638 | RandomMiiData3{ |
| 551 | .arg_1 = 0, | 639 | .arg_1 = 0, |
| 552 | .arg_2 = 0, | 640 | .arg_2 = 0, |
| 553 | .values_count = 10, | 641 | .values_count = 10, |
| 554 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, | 642 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, |
| 555 | }, | 643 | }, |
| 556 | Service::Mii::RandomMiiData3{ | 644 | RandomMiiData3{ |
| 557 | .arg_1 = 0, | 645 | .arg_1 = 0, |
| 558 | .arg_2 = 1, | 646 | .arg_2 = 1, |
| 559 | .values_count = 10, | 647 | .values_count = 10, |
| 560 | .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, | 648 | .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, |
| 561 | }, | 649 | }, |
| 562 | Service::Mii::RandomMiiData3{ | 650 | RandomMiiData3{ |
| 563 | .arg_1 = 0, | 651 | .arg_1 = 0, |
| 564 | .arg_2 = 2, | 652 | .arg_2 = 2, |
| 565 | .values_count = 10, | 653 | .values_count = 10, |
| 566 | .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, | 654 | .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, |
| 567 | }, | 655 | }, |
| 568 | Service::Mii::RandomMiiData3{ | 656 | RandomMiiData3{ |
| 569 | .arg_1 = 1, | 657 | .arg_1 = 1, |
| 570 | .arg_2 = 0, | 658 | .arg_2 = 0, |
| 571 | .values_count = 10, | 659 | .values_count = 10, |
| 572 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, | 660 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, |
| 573 | }, | 661 | }, |
| 574 | Service::Mii::RandomMiiData3{ | 662 | RandomMiiData3{ |
| 575 | .arg_1 = 1, | 663 | .arg_1 = 1, |
| 576 | .arg_2 = 1, | 664 | .arg_2 = 1, |
| 577 | .values_count = 10, | 665 | .values_count = 10, |
| 578 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, | 666 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, |
| 579 | }, | 667 | }, |
| 580 | Service::Mii::RandomMiiData3{ | 668 | RandomMiiData3{ |
| 581 | .arg_1 = 1, | 669 | .arg_1 = 1, |
| 582 | .arg_2 = 2, | 670 | .arg_2 = 2, |
| 583 | .values_count = 10, | 671 | .values_count = 10, |
| @@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ | |||
| 585 | }, | 673 | }, |
| 586 | }; | 674 | }; |
| 587 | 675 | ||
| 588 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ | 676 | const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{ |
| 589 | Service::Mii::RandomMiiData4{ | 677 | RandomMiiData4{ |
| 590 | .gender = Gender::Male, | 678 | .gender = static_cast<u32>(Gender::Male), |
| 591 | .age = Age::Young, | 679 | .age = static_cast<u32>(Age::Young), |
| 592 | .race = Race::Black, | 680 | .race = static_cast<u32>(Race::Black), |
| 593 | .values_count = 20, | 681 | .values_count = 20, |
| 594 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 682 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 595 | }, | 683 | }, |
| 596 | Service::Mii::RandomMiiData4{ | 684 | RandomMiiData4{ |
| 597 | .gender = Gender::Male, | 685 | .gender = static_cast<u32>(Gender::Male), |
| 598 | .age = Age::Normal, | 686 | .age = static_cast<u32>(Age::Normal), |
| 599 | .race = Race::Black, | 687 | .race = static_cast<u32>(Race::Black), |
| 600 | .values_count = 20, | 688 | .values_count = 20, |
| 601 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 689 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 602 | }, | 690 | }, |
| 603 | Service::Mii::RandomMiiData4{ | 691 | RandomMiiData4{ |
| 604 | .gender = Gender::Male, | 692 | .gender = static_cast<u32>(Gender::Male), |
| 605 | .age = Age::Old, | 693 | .age = static_cast<u32>(Age::Old), |
| 606 | .race = Race::Black, | 694 | .race = static_cast<u32>(Race::Black), |
| 607 | .values_count = 20, | 695 | .values_count = 20, |
| 608 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, | 696 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, |
| 609 | }, | 697 | }, |
| 610 | Service::Mii::RandomMiiData4{ | 698 | RandomMiiData4{ |
| 611 | .gender = Gender::Male, | 699 | .gender = static_cast<u32>(Gender::Male), |
| 612 | .age = Age::Young, | 700 | .age = static_cast<u32>(Age::Young), |
| 613 | .race = Race::White, | 701 | .race = static_cast<u32>(Race::White), |
| 614 | .values_count = 20, | 702 | .values_count = 20, |
| 615 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, | 703 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, |
| 616 | }, | 704 | }, |
| 617 | Service::Mii::RandomMiiData4{ | 705 | RandomMiiData4{ |
| 618 | .gender = Gender::Male, | 706 | .gender = static_cast<u32>(Gender::Male), |
| 619 | .age = Age::Normal, | 707 | .age = static_cast<u32>(Age::Normal), |
| 620 | .race = Race::White, | 708 | .race = static_cast<u32>(Race::White), |
| 621 | .values_count = 20, | 709 | .values_count = 20, |
| 622 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, | 710 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, |
| 623 | }, | 711 | }, |
| 624 | Service::Mii::RandomMiiData4{ | 712 | RandomMiiData4{ |
| 625 | .gender = Gender::Male, | 713 | .gender = static_cast<u32>(Gender::Male), |
| 626 | .age = Age::Old, | 714 | .age = static_cast<u32>(Age::Old), |
| 627 | .race = Race::White, | 715 | .race = static_cast<u32>(Race::White), |
| 628 | .values_count = 20, | 716 | .values_count = 20, |
| 629 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 717 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 630 | }, | 718 | }, |
| 631 | Service::Mii::RandomMiiData4{ | 719 | RandomMiiData4{ |
| 632 | .gender = Gender::Male, | 720 | .gender = static_cast<u32>(Gender::Male), |
| 633 | .age = Age::Young, | 721 | .age = static_cast<u32>(Age::Young), |
| 634 | .race = Race::Asian, | 722 | .race = static_cast<u32>(Race::Asian), |
| 635 | .values_count = 20, | 723 | .values_count = 20, |
| 636 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, | 724 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, |
| 637 | }, | 725 | }, |
| 638 | Service::Mii::RandomMiiData4{ | 726 | RandomMiiData4{ |
| 639 | .gender = Gender::Male, | 727 | .gender = static_cast<u32>(Gender::Male), |
| 640 | .age = Age::Normal, | 728 | .age = static_cast<u32>(Age::Normal), |
| 641 | .race = Race::Asian, | 729 | .race = static_cast<u32>(Race::Asian), |
| 642 | .values_count = 20, | 730 | .values_count = 20, |
| 643 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, | 731 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, |
| 644 | }, | 732 | }, |
| 645 | Service::Mii::RandomMiiData4{ | 733 | RandomMiiData4{ |
| 646 | .gender = Gender::Male, | 734 | .gender = static_cast<u32>(Gender::Male), |
| 647 | .age = Age::Old, | 735 | .age = static_cast<u32>(Age::Old), |
| 648 | .race = Race::Asian, | 736 | .race = static_cast<u32>(Race::Asian), |
| 649 | .values_count = 20, | 737 | .values_count = 20, |
| 650 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, | 738 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, |
| 651 | }, | 739 | }, |
| 652 | Service::Mii::RandomMiiData4{ | 740 | RandomMiiData4{ |
| 653 | .gender = Gender::Female, | 741 | .gender = static_cast<u32>(Gender::Female), |
| 654 | .age = Age::Young, | 742 | .age = static_cast<u32>(Age::Young), |
| 655 | .race = Race::Black, | 743 | .race = static_cast<u32>(Race::Black), |
| 656 | .values_count = 20, | 744 | .values_count = 20, |
| 657 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 745 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 658 | }, | 746 | }, |
| 659 | Service::Mii::RandomMiiData4{ | 747 | RandomMiiData4{ |
| 660 | .gender = Gender::Female, | 748 | .gender = static_cast<u32>(Gender::Female), |
| 661 | .age = Age::Normal, | 749 | .age = static_cast<u32>(Age::Normal), |
| 662 | .race = Race::Black, | 750 | .race = static_cast<u32>(Race::Black), |
| 663 | .values_count = 20, | 751 | .values_count = 20, |
| 664 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 752 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 665 | }, | 753 | }, |
| 666 | Service::Mii::RandomMiiData4{ | 754 | RandomMiiData4{ |
| 667 | .gender = Gender::Female, | 755 | .gender = static_cast<u32>(Gender::Female), |
| 668 | .age = Age::Old, | 756 | .age = static_cast<u32>(Age::Old), |
| 669 | .race = Race::Black, | 757 | .race = static_cast<u32>(Race::Black), |
| 670 | .values_count = 20, | 758 | .values_count = 20, |
| 671 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 759 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 672 | }, | 760 | }, |
| 673 | Service::Mii::RandomMiiData4{ | 761 | RandomMiiData4{ |
| 674 | .gender = Gender::Female, | 762 | .gender = static_cast<u32>(Gender::Female), |
| 675 | .age = Age::Young, | 763 | .age = static_cast<u32>(Age::Young), |
| 676 | .race = Race::White, | 764 | .race = static_cast<u32>(Race::White), |
| 677 | .values_count = 20, | 765 | .values_count = 20, |
| 678 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, | 766 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, |
| 679 | }, | 767 | }, |
| 680 | Service::Mii::RandomMiiData4{ | 768 | RandomMiiData4{ |
| 681 | .gender = Gender::Female, | 769 | .gender = static_cast<u32>(Gender::Female), |
| 682 | .age = Age::Normal, | 770 | .age = static_cast<u32>(Age::Normal), |
| 683 | .race = Race::White, | 771 | .race = static_cast<u32>(Race::White), |
| 684 | .values_count = 20, | 772 | .values_count = 20, |
| 685 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, | 773 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, |
| 686 | }, | 774 | }, |
| 687 | Service::Mii::RandomMiiData4{ | 775 | RandomMiiData4{ |
| 688 | .gender = Gender::Female, | 776 | .gender = static_cast<u32>(Gender::Female), |
| 689 | .age = Age::Old, | 777 | .age = static_cast<u32>(Age::Old), |
| 690 | .race = Race::White, | 778 | .race = static_cast<u32>(Race::White), |
| 691 | .values_count = 20, | 779 | .values_count = 20, |
| 692 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, | 780 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, |
| 693 | }, | 781 | }, |
| 694 | Service::Mii::RandomMiiData4{ | 782 | RandomMiiData4{ |
| 695 | .gender = Gender::Female, | 783 | .gender = static_cast<u32>(Gender::Female), |
| 696 | .age = Age::Young, | 784 | .age = static_cast<u32>(Age::Young), |
| 697 | .race = Race::Asian, | 785 | .race = static_cast<u32>(Race::Asian), |
| 698 | .values_count = 20, | 786 | .values_count = 20, |
| 699 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, | 787 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, |
| 700 | }, | 788 | }, |
| 701 | Service::Mii::RandomMiiData4{ | 789 | RandomMiiData4{ |
| 702 | .gender = Gender::Female, | 790 | .gender = static_cast<u32>(Gender::Female), |
| 703 | .age = Age::Normal, | 791 | .age = static_cast<u32>(Age::Normal), |
| 704 | .race = Race::Asian, | 792 | .race = static_cast<u32>(Race::Asian), |
| 705 | .values_count = 20, | 793 | .values_count = 20, |
| 706 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, | 794 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, |
| 707 | }, | 795 | }, |
| 708 | Service::Mii::RandomMiiData4{ | 796 | RandomMiiData4{ |
| 709 | .gender = Gender::Female, | 797 | .gender = static_cast<u32>(Gender::Female), |
| 710 | .age = Age::Old, | 798 | .age = static_cast<u32>(Age::Old), |
| 711 | .race = Race::Asian, | 799 | .race = static_cast<u32>(Race::Asian), |
| 712 | .values_count = 20, | 800 | .values_count = 20, |
| 713 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, | 801 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, |
| 714 | }, | 802 | }, |
| 715 | }; | 803 | }; |
| 716 | 804 | ||
| 717 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ | 805 | const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{ |
| 718 | Service::Mii::RandomMiiData4{ | 806 | RandomMiiData4{ |
| 719 | .gender = Gender::Male, | 807 | .gender = static_cast<u32>(Gender::Male), |
| 720 | .age = Age::Young, | 808 | .age = static_cast<u32>(Age::Young), |
| 721 | .race = Race::Black, | 809 | .race = static_cast<u32>(Race::Black), |
| 722 | .values_count = 20, | 810 | .values_count = 20, |
| 723 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 811 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| 724 | }, | 812 | }, |
| 725 | Service::Mii::RandomMiiData4{ | 813 | RandomMiiData4{ |
| 726 | .gender = Gender::Male, | 814 | .gender = static_cast<u32>(Gender::Male), |
| 727 | .age = Age::Normal, | 815 | .age = static_cast<u32>(Age::Normal), |
| 728 | .race = Race::Black, | 816 | .race = static_cast<u32>(Race::Black), |
| 729 | .values_count = 20, | 817 | .values_count = 20, |
| 730 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, | 818 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, |
| 731 | }, | 819 | }, |
| 732 | Service::Mii::RandomMiiData4{ | 820 | RandomMiiData4{ |
| 733 | .gender = Gender::Male, | 821 | .gender = static_cast<u32>(Gender::Male), |
| 734 | .age = Age::Old, | 822 | .age = static_cast<u32>(Age::Old), |
| 735 | .race = Race::Black, | 823 | .race = static_cast<u32>(Race::Black), |
| 736 | .values_count = 20, | 824 | .values_count = 20, |
| 737 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, | 825 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, |
| 738 | }, | 826 | }, |
| 739 | Service::Mii::RandomMiiData4{ | 827 | RandomMiiData4{ |
| 740 | .gender = Gender::Male, | 828 | .gender = static_cast<u32>(Gender::Male), |
| 741 | .age = Age::Young, | 829 | .age = static_cast<u32>(Age::Young), |
| 742 | .race = Race::White, | 830 | .race = static_cast<u32>(Race::White), |
| 743 | .values_count = 20, | 831 | .values_count = 20, |
| 744 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 832 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 745 | }, | 833 | }, |
| 746 | Service::Mii::RandomMiiData4{ | 834 | RandomMiiData4{ |
| 747 | .gender = Gender::Male, | 835 | .gender = static_cast<u32>(Gender::Male), |
| 748 | .age = Age::Normal, | 836 | .age = static_cast<u32>(Age::Normal), |
| 749 | .race = Race::White, | 837 | .race = static_cast<u32>(Race::White), |
| 750 | .values_count = 20, | 838 | .values_count = 20, |
| 751 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 839 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 752 | }, | 840 | }, |
| 753 | Service::Mii::RandomMiiData4{ | 841 | RandomMiiData4{ |
| 754 | .gender = Gender::Male, | 842 | .gender = static_cast<u32>(Gender::Male), |
| 755 | .age = Age::Old, | 843 | .age = static_cast<u32>(Age::Old), |
| 756 | .race = Race::White, | 844 | .race = static_cast<u32>(Race::White), |
| 757 | .values_count = 20, | 845 | .values_count = 20, |
| 758 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 846 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 759 | }, | 847 | }, |
| 760 | Service::Mii::RandomMiiData4{ | 848 | RandomMiiData4{ |
| 761 | .gender = Gender::Male, | 849 | .gender = static_cast<u32>(Gender::Male), |
| 762 | .age = Age::Young, | 850 | .age = static_cast<u32>(Age::Young), |
| 763 | .race = Race::Asian, | 851 | .race = static_cast<u32>(Race::Asian), |
| 764 | .values_count = 20, | 852 | .values_count = 20, |
| 765 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 853 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 766 | }, | 854 | }, |
| 767 | Service::Mii::RandomMiiData4{ | 855 | RandomMiiData4{ |
| 768 | .gender = Gender::Male, | 856 | .gender = static_cast<u32>(Gender::Male), |
| 769 | .age = Age::Normal, | 857 | .age = static_cast<u32>(Age::Normal), |
| 770 | .race = Race::Asian, | 858 | .race = static_cast<u32>(Race::Asian), |
| 771 | .values_count = 20, | 859 | .values_count = 20, |
| 772 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 860 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 773 | }, | 861 | }, |
| 774 | Service::Mii::RandomMiiData4{ | 862 | RandomMiiData4{ |
| 775 | .gender = Gender::Male, | 863 | .gender = static_cast<u32>(Gender::Male), |
| 776 | .age = Age::Old, | 864 | .age = static_cast<u32>(Age::Old), |
| 777 | .race = Race::Asian, | 865 | .race = static_cast<u32>(Race::Asian), |
| 778 | .values_count = 20, | 866 | .values_count = 20, |
| 779 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 867 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 780 | }, | 868 | }, |
| 781 | Service::Mii::RandomMiiData4{ | 869 | RandomMiiData4{ |
| 782 | .gender = Gender::Female, | 870 | .gender = static_cast<u32>(Gender::Female), |
| 783 | .age = Age::Young, | 871 | .age = static_cast<u32>(Age::Young), |
| 784 | .race = Race::Black, | 872 | .race = static_cast<u32>(Race::Black), |
| 785 | .values_count = 20, | 873 | .values_count = 20, |
| 786 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, | 874 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, |
| 787 | }, | 875 | }, |
| 788 | Service::Mii::RandomMiiData4{ | 876 | RandomMiiData4{ |
| 789 | .gender = Gender::Female, | 877 | .gender = static_cast<u32>(Gender::Female), |
| 790 | .age = Age::Normal, | 878 | .age = static_cast<u32>(Age::Normal), |
| 791 | .race = Race::Black, | 879 | .race = static_cast<u32>(Race::Black), |
| 792 | .values_count = 20, | 880 | .values_count = 20, |
| 793 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, | 881 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, |
| 794 | }, | 882 | }, |
| 795 | Service::Mii::RandomMiiData4{ | 883 | RandomMiiData4{ |
| 796 | .gender = Gender::Female, | 884 | .gender = static_cast<u32>(Gender::Female), |
| 797 | .age = Age::Old, | 885 | .age = static_cast<u32>(Age::Old), |
| 798 | .race = Race::Black, | 886 | .race = static_cast<u32>(Race::Black), |
| 799 | .values_count = 20, | 887 | .values_count = 20, |
| 800 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, | 888 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, |
| 801 | }, | 889 | }, |
| 802 | Service::Mii::RandomMiiData4{ | 890 | RandomMiiData4{ |
| 803 | .gender = Gender::Female, | 891 | .gender = static_cast<u32>(Gender::Female), |
| 804 | .age = Age::Young, | 892 | .age = static_cast<u32>(Age::Young), |
| 805 | .race = Race::White, | 893 | .race = static_cast<u32>(Race::White), |
| 806 | .values_count = 20, | 894 | .values_count = 20, |
| 807 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, | 895 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, |
| 808 | }, | 896 | }, |
| 809 | Service::Mii::RandomMiiData4{ | 897 | RandomMiiData4{ |
| 810 | .gender = Gender::Female, | 898 | .gender = static_cast<u32>(Gender::Female), |
| 811 | .age = Age::Normal, | 899 | .age = static_cast<u32>(Age::Normal), |
| 812 | .race = Race::White, | 900 | .race = static_cast<u32>(Race::White), |
| 813 | .values_count = 20, | 901 | .values_count = 20, |
| 814 | .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, | 902 | .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, |
| 815 | }, | 903 | }, |
| 816 | Service::Mii::RandomMiiData4{ | 904 | RandomMiiData4{ |
| 817 | .gender = Gender::Female, | 905 | .gender = static_cast<u32>(Gender::Female), |
| 818 | .age = Age::Old, | 906 | .age = static_cast<u32>(Age::Old), |
| 819 | .race = Race::White, | 907 | .race = static_cast<u32>(Race::White), |
| 820 | .values_count = 20, | 908 | .values_count = 20, |
| 821 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, | 909 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, |
| 822 | }, | 910 | }, |
| 823 | Service::Mii::RandomMiiData4{ | 911 | RandomMiiData4{ |
| 824 | .gender = Gender::Female, | 912 | .gender = static_cast<u32>(Gender::Female), |
| 825 | .age = Age::Young, | 913 | .age = static_cast<u32>(Age::Young), |
| 826 | .race = Race::Asian, | 914 | .race = static_cast<u32>(Race::Asian), |
| 827 | .values_count = 20, | 915 | .values_count = 20, |
| 828 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 916 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 829 | }, | 917 | }, |
| 830 | Service::Mii::RandomMiiData4{ | 918 | RandomMiiData4{ |
| 831 | .gender = Gender::Female, | 919 | .gender = static_cast<u32>(Gender::Female), |
| 832 | .age = Age::Normal, | 920 | .age = static_cast<u32>(Age::Normal), |
| 833 | .race = Race::Asian, | 921 | .race = static_cast<u32>(Race::Asian), |
| 834 | .values_count = 20, | 922 | .values_count = 20, |
| 835 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 923 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 836 | }, | 924 | }, |
| 837 | Service::Mii::RandomMiiData4{ | 925 | RandomMiiData4{ |
| 838 | .gender = Gender::Female, | 926 | .gender = static_cast<u32>(Gender::Female), |
| 839 | .age = Age::Old, | 927 | .age = static_cast<u32>(Age::Old), |
| 840 | .race = Race::Asian, | 928 | .race = static_cast<u32>(Race::Asian), |
| 841 | .values_count = 20, | 929 | .values_count = 20, |
| 842 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 930 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 843 | }, | 931 | }, |
| 844 | }; | 932 | }; |
| 845 | 933 | ||
| 846 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ | 934 | const std::array<RandomMiiData4, 18> RandomMiiHairType{ |
| 847 | Service::Mii::RandomMiiData4{ | 935 | RandomMiiData4{ |
| 848 | .gender = Gender::Male, | 936 | .gender = static_cast<u32>(Gender::Male), |
| 849 | .age = Age::Young, | 937 | .age = static_cast<u32>(Age::Young), |
| 850 | .race = Race::Black, | 938 | .race = static_cast<u32>(Race::Black), |
| 851 | .values_count = 30, | 939 | .values_count = 30, |
| 852 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, | 940 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, |
| 853 | 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, | 941 | 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, |
| 854 | }, | 942 | }, |
| 855 | Service::Mii::RandomMiiData4{ | 943 | RandomMiiData4{ |
| 856 | .gender = Gender::Male, | 944 | .gender = static_cast<u32>(Gender::Male), |
| 857 | .age = Age::Normal, | 945 | .age = static_cast<u32>(Age::Normal), |
| 858 | .race = Race::Black, | 946 | .race = static_cast<u32>(Race::Black), |
| 859 | .values_count = 31, | 947 | .values_count = 31, |
| 860 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, | 948 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, |
| 861 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, | 949 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, |
| 862 | }, | 950 | }, |
| 863 | Service::Mii::RandomMiiData4{ | 951 | RandomMiiData4{ |
| 864 | .gender = Gender::Male, | 952 | .gender = static_cast<u32>(Gender::Male), |
| 865 | .age = Age::Old, | 953 | .age = static_cast<u32>(Age::Old), |
| 866 | .race = Race::Black, | 954 | .race = static_cast<u32>(Race::Black), |
| 867 | .values_count = 31, | 955 | .values_count = 31, |
| 868 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, | 956 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, |
| 869 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, | 957 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, |
| 870 | }, | 958 | }, |
| 871 | Service::Mii::RandomMiiData4{ | 959 | RandomMiiData4{ |
| 872 | .gender = Gender::Male, | 960 | .gender = static_cast<u32>(Gender::Male), |
| 873 | .age = Age::Young, | 961 | .age = static_cast<u32>(Age::Young), |
| 874 | .race = Race::White, | 962 | .race = static_cast<u32>(Race::White), |
| 875 | .values_count = 38, | 963 | .values_count = 38, |
| 876 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, | 964 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, |
| 877 | 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, | 965 | 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, |
| 878 | }, | 966 | }, |
| 879 | Service::Mii::RandomMiiData4{ | 967 | RandomMiiData4{ |
| 880 | .gender = Gender::Male, | 968 | .gender = static_cast<u32>(Gender::Male), |
| 881 | .age = Age::Normal, | 969 | .age = static_cast<u32>(Age::Normal), |
| 882 | .race = Race::White, | 970 | .race = static_cast<u32>(Race::White), |
| 883 | .values_count = 39, | 971 | .values_count = 39, |
| 884 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, | 972 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, |
| 885 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, | 973 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, |
| 886 | }, | 974 | }, |
| 887 | Service::Mii::RandomMiiData4{ | 975 | RandomMiiData4{ |
| 888 | .gender = Gender::Male, | 976 | .gender = static_cast<u32>(Gender::Male), |
| 889 | .age = Age::Old, | 977 | .age = static_cast<u32>(Age::Old), |
| 890 | .race = Race::White, | 978 | .race = static_cast<u32>(Race::White), |
| 891 | .values_count = 39, | 979 | .values_count = 39, |
| 892 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, | 980 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, |
| 893 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, | 981 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, |
| 894 | }, | 982 | }, |
| 895 | Service::Mii::RandomMiiData4{ | 983 | RandomMiiData4{ |
| 896 | .gender = Gender::Male, | 984 | .gender = static_cast<u32>(Gender::Male), |
| 897 | .age = Age::Young, | 985 | .age = static_cast<u32>(Age::Young), |
| 898 | .race = Race::Asian, | 986 | .race = static_cast<u32>(Race::Asian), |
| 899 | .values_count = 18, | 987 | .values_count = 18, |
| 900 | .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, | 988 | .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, |
| 901 | }, | 989 | }, |
| 902 | Service::Mii::RandomMiiData4{ | 990 | RandomMiiData4{ |
| 903 | .gender = Gender::Male, | 991 | .gender = static_cast<u32>(Gender::Male), |
| 904 | .age = Age::Normal, | 992 | .age = static_cast<u32>(Age::Normal), |
| 905 | .race = Race::Asian, | 993 | .race = static_cast<u32>(Race::Asian), |
| 906 | .values_count = 19, | 994 | .values_count = 19, |
| 907 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, | 995 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, |
| 908 | }, | 996 | }, |
| 909 | Service::Mii::RandomMiiData4{ | 997 | RandomMiiData4{ |
| 910 | .gender = Gender::Male, | 998 | .gender = static_cast<u32>(Gender::Male), |
| 911 | .age = Age::Old, | 999 | .age = static_cast<u32>(Age::Old), |
| 912 | .race = Race::Asian, | 1000 | .race = static_cast<u32>(Race::Asian), |
| 913 | .values_count = 19, | 1001 | .values_count = 19, |
| 914 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, | 1002 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, |
| 915 | }, | 1003 | }, |
| 916 | Service::Mii::RandomMiiData4{ | 1004 | RandomMiiData4{ |
| 917 | .gender = Gender::Female, | 1005 | .gender = static_cast<u32>(Gender::Female), |
| 918 | .age = Age::Young, | 1006 | .age = static_cast<u32>(Age::Young), |
| 919 | .race = Race::Black, | 1007 | .race = static_cast<u32>(Race::Black), |
| 920 | .values_count = 39, | 1008 | .values_count = 39, |
| 921 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, | 1009 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, |
| 922 | 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, | 1010 | 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, |
| 923 | }, | 1011 | }, |
| 924 | Service::Mii::RandomMiiData4{ | 1012 | RandomMiiData4{ |
| 925 | .gender = Gender::Female, | 1013 | .gender = static_cast<u32>(Gender::Female), |
| 926 | .age = Age::Normal, | 1014 | .age = static_cast<u32>(Age::Normal), |
| 927 | .race = Race::Black, | 1015 | .race = static_cast<u32>(Race::Black), |
| 928 | .values_count = 42, | 1016 | .values_count = 42, |
| 929 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, | 1017 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, |
| 930 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, | 1018 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, |
| 931 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, | 1019 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, |
| 932 | }, | 1020 | }, |
| 933 | Service::Mii::RandomMiiData4{ | 1021 | RandomMiiData4{ |
| 934 | .gender = Gender::Female, | 1022 | .gender = static_cast<u32>(Gender::Female), |
| 935 | .age = Age::Old, | 1023 | .age = static_cast<u32>(Age::Old), |
| 936 | .race = Race::Black, | 1024 | .race = static_cast<u32>(Race::Black), |
| 937 | .values_count = 42, | 1025 | .values_count = 42, |
| 938 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, | 1026 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, |
| 939 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, | 1027 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, |
| 940 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, | 1028 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, |
| 941 | }, | 1029 | }, |
| 942 | Service::Mii::RandomMiiData4{ | 1030 | RandomMiiData4{ |
| 943 | .gender = Gender::Female, | 1031 | .gender = static_cast<u32>(Gender::Female), |
| 944 | .age = Age::Young, | 1032 | .age = static_cast<u32>(Age::Young), |
| 945 | .race = Race::White, | 1033 | .race = static_cast<u32>(Race::White), |
| 946 | .values_count = 44, | 1034 | .values_count = 44, |
| 947 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 1035 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 948 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, | 1036 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, |
| 949 | 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, | 1037 | 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, |
| 950 | }, | 1038 | }, |
| 951 | Service::Mii::RandomMiiData4{ | 1039 | RandomMiiData4{ |
| 952 | .gender = Gender::Female, | 1040 | .gender = static_cast<u32>(Gender::Female), |
| 953 | .age = Age::Normal, | 1041 | .age = static_cast<u32>(Age::Normal), |
| 954 | .race = Race::White, | 1042 | .race = static_cast<u32>(Race::White), |
| 955 | .values_count = 44, | 1043 | .values_count = 44, |
| 956 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 1044 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 957 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, | 1045 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, |
| 958 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, | 1046 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, |
| 959 | }, | 1047 | }, |
| 960 | Service::Mii::RandomMiiData4{ | 1048 | RandomMiiData4{ |
| 961 | .gender = Gender::Female, | 1049 | .gender = static_cast<u32>(Gender::Female), |
| 962 | .age = Age::Old, | 1050 | .age = static_cast<u32>(Age::Old), |
| 963 | .race = Race::White, | 1051 | .race = static_cast<u32>(Race::White), |
| 964 | .values_count = 44, | 1052 | .values_count = 44, |
| 965 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 1053 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 966 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, | 1054 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, |
| 967 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, | 1055 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, |
| 968 | }, | 1056 | }, |
| 969 | Service::Mii::RandomMiiData4{ | 1057 | RandomMiiData4{ |
| 970 | .gender = Gender::Female, | 1058 | .gender = static_cast<u32>(Gender::Female), |
| 971 | .age = Age::Young, | 1059 | .age = static_cast<u32>(Age::Young), |
| 972 | .race = Race::Asian, | 1060 | .race = static_cast<u32>(Race::Asian), |
| 973 | .values_count = 24, | 1061 | .values_count = 24, |
| 974 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, | 1062 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, |
| 975 | 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, | 1063 | 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, |
| 976 | }, | 1064 | }, |
| 977 | Service::Mii::RandomMiiData4{ | 1065 | RandomMiiData4{ |
| 978 | .gender = Gender::Female, | 1066 | .gender = static_cast<u32>(Gender::Female), |
| 979 | .age = Age::Normal, | 1067 | .age = static_cast<u32>(Age::Normal), |
| 980 | .race = Race::Asian, | 1068 | .race = static_cast<u32>(Race::Asian), |
| 981 | .values_count = 27, | 1069 | .values_count = 27, |
| 982 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, | 1070 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, |
| 983 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, | 1071 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, |
| 984 | }, | 1072 | }, |
| 985 | Service::Mii::RandomMiiData4{ | 1073 | RandomMiiData4{ |
| 986 | .gender = Gender::Female, | 1074 | .gender = static_cast<u32>(Gender::Female), |
| 987 | .age = Age::Old, | 1075 | .age = static_cast<u32>(Age::Old), |
| 988 | .race = Race::Asian, | 1076 | .race = static_cast<u32>(Race::Asian), |
| 989 | .values_count = 27, | 1077 | .values_count = 27, |
| 990 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, | 1078 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, |
| 991 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, | 1079 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, |
| @@ -993,55 +1081,55 @@ const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ | |||
| 993 | }; | 1081 | }; |
| 994 | 1082 | ||
| 995 | const std::array<RandomMiiData3, 9> RandomMiiHairColor{ | 1083 | const std::array<RandomMiiData3, 9> RandomMiiHairColor{ |
| 996 | Service::Mii::RandomMiiData3{ | 1084 | RandomMiiData3{ |
| 997 | .arg_1 = 0, | 1085 | .arg_1 = 0, |
| 998 | .arg_2 = 0, | 1086 | .arg_2 = 0, |
| 999 | .values_count = 20, | 1087 | .values_count = 20, |
| 1000 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 1088 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| 1001 | }, | 1089 | }, |
| 1002 | Service::Mii::RandomMiiData3{ | 1090 | RandomMiiData3{ |
| 1003 | .arg_1 = 0, | 1091 | .arg_1 = 0, |
| 1004 | .arg_2 = 1, | 1092 | .arg_2 = 1, |
| 1005 | .values_count = 20, | 1093 | .values_count = 20, |
| 1006 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 1094 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| 1007 | }, | 1095 | }, |
| 1008 | Service::Mii::RandomMiiData3{ | 1096 | RandomMiiData3{ |
| 1009 | .arg_1 = 0, | 1097 | .arg_1 = 0, |
| 1010 | .arg_2 = 2, | 1098 | .arg_2 = 2, |
| 1011 | .values_count = 20, | 1099 | .values_count = 20, |
| 1012 | .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, | 1100 | .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, |
| 1013 | }, | 1101 | }, |
| 1014 | Service::Mii::RandomMiiData3{ | 1102 | RandomMiiData3{ |
| 1015 | .arg_1 = 1, | 1103 | .arg_1 = 1, |
| 1016 | .arg_2 = 0, | 1104 | .arg_2 = 0, |
| 1017 | .values_count = 20, | 1105 | .values_count = 20, |
| 1018 | .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, | 1106 | .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, |
| 1019 | }, | 1107 | }, |
| 1020 | Service::Mii::RandomMiiData3{ | 1108 | RandomMiiData3{ |
| 1021 | .arg_1 = 1, | 1109 | .arg_1 = 1, |
| 1022 | .arg_2 = 1, | 1110 | .arg_2 = 1, |
| 1023 | .values_count = 20, | 1111 | .values_count = 20, |
| 1024 | .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, | 1112 | .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, |
| 1025 | }, | 1113 | }, |
| 1026 | Service::Mii::RandomMiiData3{ | 1114 | RandomMiiData3{ |
| 1027 | .arg_1 = 1, | 1115 | .arg_1 = 1, |
| 1028 | .arg_2 = 2, | 1116 | .arg_2 = 2, |
| 1029 | .values_count = 20, | 1117 | .values_count = 20, |
| 1030 | .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, | 1118 | .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, |
| 1031 | }, | 1119 | }, |
| 1032 | Service::Mii::RandomMiiData3{ | 1120 | RandomMiiData3{ |
| 1033 | .arg_1 = 2, | 1121 | .arg_1 = 2, |
| 1034 | .arg_2 = 0, | 1122 | .arg_2 = 0, |
| 1035 | .values_count = 20, | 1123 | .values_count = 20, |
| 1036 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, | 1124 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, |
| 1037 | }, | 1125 | }, |
| 1038 | Service::Mii::RandomMiiData3{ | 1126 | RandomMiiData3{ |
| 1039 | .arg_1 = 2, | 1127 | .arg_1 = 2, |
| 1040 | .arg_2 = 1, | 1128 | .arg_2 = 1, |
| 1041 | .values_count = 20, | 1129 | .values_count = 20, |
| 1042 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, | 1130 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, |
| 1043 | }, | 1131 | }, |
| 1044 | Service::Mii::RandomMiiData3{ | 1132 | RandomMiiData3{ |
| 1045 | .arg_1 = 2, | 1133 | .arg_1 = 2, |
| 1046 | .arg_2 = 2, | 1134 | .arg_2 = 2, |
| 1047 | .values_count = 20, | 1135 | .values_count = 20, |
| @@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{ | |||
| 1049 | }, | 1137 | }, |
| 1050 | }; | 1138 | }; |
| 1051 | 1139 | ||
| 1052 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ | 1140 | const std::array<RandomMiiData4, 18> RandomMiiEyeType{ |
| 1053 | Service::Mii::RandomMiiData4{ | 1141 | RandomMiiData4{ |
| 1054 | .gender = Gender::Male, | 1142 | .gender = static_cast<u32>(Gender::Male), |
| 1055 | .age = Age::Young, | 1143 | .age = static_cast<u32>(Age::Young), |
| 1056 | .race = Race::Black, | 1144 | .race = static_cast<u32>(Race::Black), |
| 1057 | .values_count = 26, | 1145 | .values_count = 26, |
| 1058 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, | 1146 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, |
| 1059 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, | 1147 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, |
| 1060 | }, | 1148 | }, |
| 1061 | Service::Mii::RandomMiiData4{ | 1149 | RandomMiiData4{ |
| 1062 | .gender = Gender::Male, | 1150 | .gender = static_cast<u32>(Gender::Male), |
| 1063 | .age = Age::Normal, | 1151 | .age = static_cast<u32>(Age::Normal), |
| 1064 | .race = Race::Black, | 1152 | .race = static_cast<u32>(Race::Black), |
| 1065 | .values_count = 26, | 1153 | .values_count = 26, |
| 1066 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, | 1154 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, |
| 1067 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, | 1155 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, |
| 1068 | }, | 1156 | }, |
| 1069 | Service::Mii::RandomMiiData4{ | 1157 | RandomMiiData4{ |
| 1070 | .gender = Gender::Male, | 1158 | .gender = static_cast<u32>(Gender::Male), |
| 1071 | .age = Age::Old, | 1159 | .age = static_cast<u32>(Age::Old), |
| 1072 | .race = Race::Black, | 1160 | .race = static_cast<u32>(Race::Black), |
| 1073 | .values_count = 27, | 1161 | .values_count = 27, |
| 1074 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, | 1162 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, |
| 1075 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, | 1163 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, |
| 1076 | }, | 1164 | }, |
| 1077 | Service::Mii::RandomMiiData4{ | 1165 | RandomMiiData4{ |
| 1078 | .gender = Gender::Male, | 1166 | .gender = static_cast<u32>(Gender::Male), |
| 1079 | .age = Age::Young, | 1167 | .age = static_cast<u32>(Age::Young), |
| 1080 | .race = Race::White, | 1168 | .race = static_cast<u32>(Race::White), |
| 1081 | .values_count = 35, | 1169 | .values_count = 35, |
| 1082 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, | 1170 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, |
| 1083 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, | 1171 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, |
| 1084 | }, | 1172 | }, |
| 1085 | Service::Mii::RandomMiiData4{ | 1173 | RandomMiiData4{ |
| 1086 | .gender = Gender::Male, | 1174 | .gender = static_cast<u32>(Gender::Male), |
| 1087 | .age = Age::Normal, | 1175 | .age = static_cast<u32>(Age::Normal), |
| 1088 | .race = Race::White, | 1176 | .race = static_cast<u32>(Race::White), |
| 1089 | .values_count = 35, | 1177 | .values_count = 35, |
| 1090 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, | 1178 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, |
| 1091 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, | 1179 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, |
| 1092 | }, | 1180 | }, |
| 1093 | Service::Mii::RandomMiiData4{ | 1181 | RandomMiiData4{ |
| 1094 | .gender = Gender::Male, | 1182 | .gender = static_cast<u32>(Gender::Male), |
| 1095 | .age = Age::Old, | 1183 | .age = static_cast<u32>(Age::Old), |
| 1096 | .race = Race::White, | 1184 | .race = static_cast<u32>(Race::White), |
| 1097 | .values_count = 35, | 1185 | .values_count = 35, |
| 1098 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, | 1186 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, |
| 1099 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, | 1187 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, |
| 1100 | }, | 1188 | }, |
| 1101 | Service::Mii::RandomMiiData4{ | 1189 | RandomMiiData4{ |
| 1102 | .gender = Gender::Male, | 1190 | .gender = static_cast<u32>(Gender::Male), |
| 1103 | .age = Age::Young, | 1191 | .age = static_cast<u32>(Age::Young), |
| 1104 | .race = Race::Asian, | 1192 | .race = static_cast<u32>(Race::Asian), |
| 1105 | .values_count = 30, | 1193 | .values_count = 30, |
| 1106 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, | 1194 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, |
| 1107 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, | 1195 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, |
| 1108 | }, | 1196 | }, |
| 1109 | Service::Mii::RandomMiiData4{ | 1197 | RandomMiiData4{ |
| 1110 | .gender = Gender::Male, | 1198 | .gender = static_cast<u32>(Gender::Male), |
| 1111 | .age = Age::Normal, | 1199 | .age = static_cast<u32>(Age::Normal), |
| 1112 | .race = Race::Asian, | 1200 | .race = static_cast<u32>(Race::Asian), |
| 1113 | .values_count = 30, | 1201 | .values_count = 30, |
| 1114 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, | 1202 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, |
| 1115 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, | 1203 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, |
| 1116 | }, | 1204 | }, |
| 1117 | Service::Mii::RandomMiiData4{ | 1205 | RandomMiiData4{ |
| 1118 | .gender = Gender::Male, | 1206 | .gender = static_cast<u32>(Gender::Male), |
| 1119 | .age = Age::Old, | 1207 | .age = static_cast<u32>(Age::Old), |
| 1120 | .race = Race::Asian, | 1208 | .race = static_cast<u32>(Race::Asian), |
| 1121 | .values_count = 30, | 1209 | .values_count = 30, |
| 1122 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, | 1210 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, |
| 1123 | 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, | 1211 | 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, |
| 1124 | }, | 1212 | }, |
| 1125 | Service::Mii::RandomMiiData4{ | 1213 | RandomMiiData4{ |
| 1126 | .gender = Gender::Female, | 1214 | .gender = static_cast<u32>(Gender::Female), |
| 1127 | .age = Age::Young, | 1215 | .age = static_cast<u32>(Age::Young), |
| 1128 | .race = Race::Black, | 1216 | .race = static_cast<u32>(Race::Black), |
| 1129 | .values_count = 39, | 1217 | .values_count = 39, |
| 1130 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, | 1218 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, |
| 1131 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, | 1219 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, |
| 1132 | }, | 1220 | }, |
| 1133 | Service::Mii::RandomMiiData4{ | 1221 | RandomMiiData4{ |
| 1134 | .gender = Gender::Female, | 1222 | .gender = static_cast<u32>(Gender::Female), |
| 1135 | .age = Age::Normal, | 1223 | .age = static_cast<u32>(Age::Normal), |
| 1136 | .race = Race::Black, | 1224 | .race = static_cast<u32>(Race::Black), |
| 1137 | .values_count = 39, | 1225 | .values_count = 39, |
| 1138 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, | 1226 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, |
| 1139 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, | 1227 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, |
| 1140 | }, | 1228 | }, |
| 1141 | Service::Mii::RandomMiiData4{ | 1229 | RandomMiiData4{ |
| 1142 | .gender = Gender::Female, | 1230 | .gender = static_cast<u32>(Gender::Female), |
| 1143 | .age = Age::Old, | 1231 | .age = static_cast<u32>(Age::Old), |
| 1144 | .race = Race::Black, | 1232 | .race = static_cast<u32>(Race::Black), |
| 1145 | .values_count = 40, | 1233 | .values_count = 40, |
| 1146 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, | 1234 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, |
| 1147 | 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, | 1235 | 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, |
| 1148 | }, | 1236 | }, |
| 1149 | Service::Mii::RandomMiiData4{ | 1237 | RandomMiiData4{ |
| 1150 | .gender = Gender::Female, | 1238 | .gender = static_cast<u32>(Gender::Female), |
| 1151 | .age = Age::Young, | 1239 | .age = static_cast<u32>(Age::Young), |
| 1152 | .race = Race::White, | 1240 | .race = static_cast<u32>(Race::White), |
| 1153 | .values_count = 46, | 1241 | .values_count = 46, |
| 1154 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, | 1242 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, |
| 1155 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, | 1243 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, |
| 1156 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, | 1244 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, |
| 1157 | }, | 1245 | }, |
| 1158 | Service::Mii::RandomMiiData4{ | 1246 | RandomMiiData4{ |
| 1159 | .gender = Gender::Female, | 1247 | .gender = static_cast<u32>(Gender::Female), |
| 1160 | .age = Age::Normal, | 1248 | .age = static_cast<u32>(Age::Normal), |
| 1161 | .race = Race::White, | 1249 | .race = static_cast<u32>(Race::White), |
| 1162 | .values_count = 46, | 1250 | .values_count = 46, |
| 1163 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, | 1251 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, |
| 1164 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, | 1252 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, |
| 1165 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, | 1253 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, |
| 1166 | }, | 1254 | }, |
| 1167 | Service::Mii::RandomMiiData4{ | 1255 | RandomMiiData4{ |
| 1168 | .gender = Gender::Female, | 1256 | .gender = static_cast<u32>(Gender::Female), |
| 1169 | .age = Age::Old, | 1257 | .age = static_cast<u32>(Age::Old), |
| 1170 | .race = Race::White, | 1258 | .race = static_cast<u32>(Race::White), |
| 1171 | .values_count = 46, | 1259 | .values_count = 46, |
| 1172 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, | 1260 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, |
| 1173 | 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, | 1261 | 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, |
| 1174 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, | 1262 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, |
| 1175 | }, | 1263 | }, |
| 1176 | Service::Mii::RandomMiiData4{ | 1264 | RandomMiiData4{ |
| 1177 | .gender = Gender::Female, | 1265 | .gender = static_cast<u32>(Gender::Female), |
| 1178 | .age = Age::Young, | 1266 | .age = static_cast<u32>(Age::Young), |
| 1179 | .race = Race::Asian, | 1267 | .race = static_cast<u32>(Race::Asian), |
| 1180 | .values_count = 34, | 1268 | .values_count = 34, |
| 1181 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, | 1269 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, |
| 1182 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, | 1270 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, |
| 1183 | }, | 1271 | }, |
| 1184 | Service::Mii::RandomMiiData4{ | 1272 | RandomMiiData4{ |
| 1185 | .gender = Gender::Female, | 1273 | .gender = static_cast<u32>(Gender::Female), |
| 1186 | .age = Age::Normal, | 1274 | .age = static_cast<u32>(Age::Normal), |
| 1187 | .race = Race::Asian, | 1275 | .race = static_cast<u32>(Race::Asian), |
| 1188 | .values_count = 34, | 1276 | .values_count = 34, |
| 1189 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, | 1277 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, |
| 1190 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, | 1278 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, |
| 1191 | }, | 1279 | }, |
| 1192 | Service::Mii::RandomMiiData4{ | 1280 | RandomMiiData4{ |
| 1193 | .gender = Gender::Female, | 1281 | .gender = static_cast<u32>(Gender::Female), |
| 1194 | .age = Age::Old, | 1282 | .age = static_cast<u32>(Age::Old), |
| 1195 | .race = Race::Asian, | 1283 | .race = static_cast<u32>(Race::Asian), |
| 1196 | .values_count = 35, | 1284 | .values_count = 35, |
| 1197 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, | 1285 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, |
| 1198 | 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, | 1286 | 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, |
| 1199 | }, | 1287 | }, |
| 1200 | }; | 1288 | }; |
| 1201 | 1289 | ||
| 1202 | const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ | 1290 | const std::array<RandomMiiData2, 3> RandomMiiEyeColor{ |
| 1203 | Service::Mii::RandomMiiData2{ | 1291 | RandomMiiData2{ |
| 1204 | .arg_1 = 0, | 1292 | .arg_1 = 0, |
| 1205 | .values_count = 10, | 1293 | .values_count = 10, |
| 1206 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, | 1294 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, |
| 1207 | }, | 1295 | }, |
| 1208 | Service::Mii::RandomMiiData2{ | 1296 | RandomMiiData2{ |
| 1209 | .arg_1 = 1, | 1297 | .arg_1 = 1, |
| 1210 | .values_count = 10, | 1298 | .values_count = 10, |
| 1211 | .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, | 1299 | .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, |
| 1212 | }, | 1300 | }, |
| 1213 | Service::Mii::RandomMiiData2{ | 1301 | RandomMiiData2{ |
| 1214 | .arg_1 = 2, | 1302 | .arg_1 = 2, |
| 1215 | .values_count = 10, | 1303 | .values_count = 10, |
| 1216 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, | 1304 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, |
| 1217 | }, | 1305 | }, |
| 1218 | }; | 1306 | }; |
| 1219 | 1307 | ||
| 1220 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ | 1308 | const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{ |
| 1221 | Service::Mii::RandomMiiData4{ | 1309 | RandomMiiData4{ |
| 1222 | .gender = Gender::Male, | 1310 | .gender = static_cast<u32>(Gender::Male), |
| 1223 | .age = Age::Young, | 1311 | .age = static_cast<u32>(Age::Young), |
| 1224 | .race = Race::Black, | 1312 | .race = static_cast<u32>(Race::Black), |
| 1225 | .values_count = 18, | 1313 | .values_count = 18, |
| 1226 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, | 1314 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, |
| 1227 | }, | 1315 | }, |
| 1228 | Service::Mii::RandomMiiData4{ | 1316 | RandomMiiData4{ |
| 1229 | .gender = Gender::Male, | 1317 | .gender = static_cast<u32>(Gender::Male), |
| 1230 | .age = Age::Normal, | 1318 | .age = static_cast<u32>(Age::Normal), |
| 1231 | .race = Race::Black, | 1319 | .race = static_cast<u32>(Race::Black), |
| 1232 | .values_count = 18, | 1320 | .values_count = 18, |
| 1233 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, | 1321 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, |
| 1234 | }, | 1322 | }, |
| 1235 | Service::Mii::RandomMiiData4{ | 1323 | RandomMiiData4{ |
| 1236 | .gender = Gender::Male, | 1324 | .gender = static_cast<u32>(Gender::Male), |
| 1237 | .age = Age::Old, | 1325 | .age = static_cast<u32>(Age::Old), |
| 1238 | .race = Race::Black, | 1326 | .race = static_cast<u32>(Race::Black), |
| 1239 | .values_count = 18, | 1327 | .values_count = 18, |
| 1240 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, | 1328 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, |
| 1241 | }, | 1329 | }, |
| 1242 | Service::Mii::RandomMiiData4{ | 1330 | RandomMiiData4{ |
| 1243 | .gender = Gender::Male, | 1331 | .gender = static_cast<u32>(Gender::Male), |
| 1244 | .age = Age::Young, | 1332 | .age = static_cast<u32>(Age::Young), |
| 1245 | .race = Race::White, | 1333 | .race = static_cast<u32>(Race::White), |
| 1246 | .values_count = 23, | 1334 | .values_count = 23, |
| 1247 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | 1335 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 1248 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, | 1336 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, |
| 1249 | }, | 1337 | }, |
| 1250 | Service::Mii::RandomMiiData4{ | 1338 | RandomMiiData4{ |
| 1251 | .gender = Gender::Male, | 1339 | .gender = static_cast<u32>(Gender::Male), |
| 1252 | .age = Age::Normal, | 1340 | .age = static_cast<u32>(Age::Normal), |
| 1253 | .race = Race::White, | 1341 | .race = static_cast<u32>(Race::White), |
| 1254 | .values_count = 23, | 1342 | .values_count = 23, |
| 1255 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | 1343 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 1256 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, | 1344 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, |
| 1257 | }, | 1345 | }, |
| 1258 | Service::Mii::RandomMiiData4{ | 1346 | RandomMiiData4{ |
| 1259 | .gender = Gender::Male, | 1347 | .gender = static_cast<u32>(Gender::Male), |
| 1260 | .age = Age::Old, | 1348 | .age = static_cast<u32>(Age::Old), |
| 1261 | .race = Race::White, | 1349 | .race = static_cast<u32>(Race::White), |
| 1262 | .values_count = 23, | 1350 | .values_count = 23, |
| 1263 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | 1351 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 1264 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, | 1352 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, |
| 1265 | }, | 1353 | }, |
| 1266 | Service::Mii::RandomMiiData4{ | 1354 | RandomMiiData4{ |
| 1267 | .gender = Gender::Male, | 1355 | .gender = static_cast<u32>(Gender::Male), |
| 1268 | .age = Age::Young, | 1356 | .age = static_cast<u32>(Age::Young), |
| 1269 | .race = Race::Asian, | 1357 | .race = static_cast<u32>(Race::Asian), |
| 1270 | .values_count = 21, | 1358 | .values_count = 21, |
| 1271 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, | 1359 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, |
| 1272 | }, | 1360 | }, |
| 1273 | Service::Mii::RandomMiiData4{ | 1361 | RandomMiiData4{ |
| 1274 | .gender = Gender::Male, | 1362 | .gender = static_cast<u32>(Gender::Male), |
| 1275 | .age = Age::Normal, | 1363 | .age = static_cast<u32>(Age::Normal), |
| 1276 | .race = Race::Asian, | 1364 | .race = static_cast<u32>(Race::Asian), |
| 1277 | .values_count = 21, | 1365 | .values_count = 21, |
| 1278 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, | 1366 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, |
| 1279 | }, | 1367 | }, |
| 1280 | Service::Mii::RandomMiiData4{ | 1368 | RandomMiiData4{ |
| 1281 | .gender = Gender::Male, | 1369 | .gender = static_cast<u32>(Gender::Male), |
| 1282 | .age = Age::Old, | 1370 | .age = static_cast<u32>(Age::Old), |
| 1283 | .race = Race::Asian, | 1371 | .race = static_cast<u32>(Race::Asian), |
| 1284 | .values_count = 21, | 1372 | .values_count = 21, |
| 1285 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, | 1373 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, |
| 1286 | }, | 1374 | }, |
| 1287 | Service::Mii::RandomMiiData4{ | 1375 | RandomMiiData4{ |
| 1288 | .gender = Gender::Female, | 1376 | .gender = static_cast<u32>(Gender::Female), |
| 1289 | .age = Age::Young, | 1377 | .age = static_cast<u32>(Age::Young), |
| 1290 | .race = Race::Black, | 1378 | .race = static_cast<u32>(Race::Black), |
| 1291 | .values_count = 9, | 1379 | .values_count = 9, |
| 1292 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, | 1380 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, |
| 1293 | }, | 1381 | }, |
| 1294 | Service::Mii::RandomMiiData4{ | 1382 | RandomMiiData4{ |
| 1295 | .gender = Gender::Female, | 1383 | .gender = static_cast<u32>(Gender::Female), |
| 1296 | .age = Age::Normal, | 1384 | .age = static_cast<u32>(Age::Normal), |
| 1297 | .race = Race::Black, | 1385 | .race = static_cast<u32>(Race::Black), |
| 1298 | .values_count = 9, | 1386 | .values_count = 9, |
| 1299 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, | 1387 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, |
| 1300 | }, | 1388 | }, |
| 1301 | Service::Mii::RandomMiiData4{ | 1389 | RandomMiiData4{ |
| 1302 | .gender = Gender::Female, | 1390 | .gender = static_cast<u32>(Gender::Female), |
| 1303 | .age = Age::Old, | 1391 | .age = static_cast<u32>(Age::Old), |
| 1304 | .race = Race::Black, | 1392 | .race = static_cast<u32>(Race::Black), |
| 1305 | .values_count = 9, | 1393 | .values_count = 9, |
| 1306 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, | 1394 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, |
| 1307 | }, | 1395 | }, |
| 1308 | Service::Mii::RandomMiiData4{ | 1396 | RandomMiiData4{ |
| 1309 | .gender = Gender::Female, | 1397 | .gender = static_cast<u32>(Gender::Female), |
| 1310 | .age = Age::Young, | 1398 | .age = static_cast<u32>(Age::Young), |
| 1311 | .race = Race::White, | 1399 | .race = static_cast<u32>(Race::White), |
| 1312 | .values_count = 11, | 1400 | .values_count = 11, |
| 1313 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, | 1401 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, |
| 1314 | }, | 1402 | }, |
| 1315 | Service::Mii::RandomMiiData4{ | 1403 | RandomMiiData4{ |
| 1316 | .gender = Gender::Female, | 1404 | .gender = static_cast<u32>(Gender::Female), |
| 1317 | .age = Age::Normal, | 1405 | .age = static_cast<u32>(Age::Normal), |
| 1318 | .race = Race::White, | 1406 | .race = static_cast<u32>(Race::White), |
| 1319 | .values_count = 11, | 1407 | .values_count = 11, |
| 1320 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, | 1408 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, |
| 1321 | }, | 1409 | }, |
| 1322 | Service::Mii::RandomMiiData4{ | 1410 | RandomMiiData4{ |
| 1323 | .gender = Gender::Female, | 1411 | .gender = static_cast<u32>(Gender::Female), |
| 1324 | .age = Age::Old, | 1412 | .age = static_cast<u32>(Age::Old), |
| 1325 | .race = Race::White, | 1413 | .race = static_cast<u32>(Race::White), |
| 1326 | .values_count = 11, | 1414 | .values_count = 11, |
| 1327 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, | 1415 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, |
| 1328 | }, | 1416 | }, |
| 1329 | Service::Mii::RandomMiiData4{ | 1417 | RandomMiiData4{ |
| 1330 | .gender = Gender::Female, | 1418 | .gender = static_cast<u32>(Gender::Female), |
| 1331 | .age = Age::Young, | 1419 | .age = static_cast<u32>(Age::Young), |
| 1332 | .race = Race::Asian, | 1420 | .race = static_cast<u32>(Race::Asian), |
| 1333 | .values_count = 9, | 1421 | .values_count = 9, |
| 1334 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, | 1422 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, |
| 1335 | }, | 1423 | }, |
| 1336 | Service::Mii::RandomMiiData4{ | 1424 | RandomMiiData4{ |
| 1337 | .gender = Gender::Female, | 1425 | .gender = static_cast<u32>(Gender::Female), |
| 1338 | .age = Age::Normal, | 1426 | .age = static_cast<u32>(Age::Normal), |
| 1339 | .race = Race::Asian, | 1427 | .race = static_cast<u32>(Race::Asian), |
| 1340 | .values_count = 9, | 1428 | .values_count = 9, |
| 1341 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, | 1429 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, |
| 1342 | }, | 1430 | }, |
| 1343 | Service::Mii::RandomMiiData4{ | 1431 | RandomMiiData4{ |
| 1344 | .gender = Gender::Female, | 1432 | .gender = static_cast<u32>(Gender::Female), |
| 1345 | .age = Age::Old, | 1433 | .age = static_cast<u32>(Age::Old), |
| 1346 | .race = Race::Asian, | 1434 | .race = static_cast<u32>(Race::Asian), |
| 1347 | .values_count = 9, | 1435 | .values_count = 9, |
| 1348 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, | 1436 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, |
| 1349 | }, | 1437 | }, |
| 1350 | }; | 1438 | }; |
| 1351 | 1439 | ||
| 1352 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ | 1440 | const std::array<RandomMiiData4, 18> RandomMiiNoseType{ |
| 1353 | Service::Mii::RandomMiiData4{ | 1441 | RandomMiiData4{ |
| 1354 | .gender = Gender::Male, | 1442 | .gender = static_cast<u32>(Gender::Male), |
| 1355 | .age = Age::Young, | 1443 | .age = static_cast<u32>(Age::Young), |
| 1356 | .race = Race::Black, | 1444 | .race = static_cast<u32>(Race::Black), |
| 1357 | .values_count = 11, | 1445 | .values_count = 11, |
| 1358 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, | 1446 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, |
| 1359 | }, | 1447 | }, |
| 1360 | Service::Mii::RandomMiiData4{ | 1448 | RandomMiiData4{ |
| 1361 | .gender = Gender::Male, | 1449 | .gender = static_cast<u32>(Gender::Male), |
| 1362 | .age = Age::Normal, | 1450 | .age = static_cast<u32>(Age::Normal), |
| 1363 | .race = Race::Black, | 1451 | .race = static_cast<u32>(Race::Black), |
| 1364 | .values_count = 11, | 1452 | .values_count = 11, |
| 1365 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, | 1453 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, |
| 1366 | }, | 1454 | }, |
| 1367 | Service::Mii::RandomMiiData4{ | 1455 | RandomMiiData4{ |
| 1368 | .gender = Gender::Male, | 1456 | .gender = static_cast<u32>(Gender::Male), |
| 1369 | .age = Age::Old, | 1457 | .age = static_cast<u32>(Age::Old), |
| 1370 | .race = Race::Black, | 1458 | .race = static_cast<u32>(Race::Black), |
| 1371 | .values_count = 11, | 1459 | .values_count = 11, |
| 1372 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, | 1460 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, |
| 1373 | }, | 1461 | }, |
| 1374 | Service::Mii::RandomMiiData4{ | 1462 | RandomMiiData4{ |
| 1375 | .gender = Gender::Male, | 1463 | .gender = static_cast<u32>(Gender::Male), |
| 1376 | .age = Age::Young, | 1464 | .age = static_cast<u32>(Age::Young), |
| 1377 | .race = Race::White, | 1465 | .race = static_cast<u32>(Race::White), |
| 1378 | .values_count = 18, | 1466 | .values_count = 18, |
| 1379 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1467 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1380 | }, | 1468 | }, |
| 1381 | Service::Mii::RandomMiiData4{ | 1469 | RandomMiiData4{ |
| 1382 | .gender = Gender::Male, | 1470 | .gender = static_cast<u32>(Gender::Male), |
| 1383 | .age = Age::Normal, | 1471 | .age = static_cast<u32>(Age::Normal), |
| 1384 | .race = Race::White, | 1472 | .race = static_cast<u32>(Race::White), |
| 1385 | .values_count = 18, | 1473 | .values_count = 18, |
| 1386 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1474 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1387 | }, | 1475 | }, |
| 1388 | Service::Mii::RandomMiiData4{ | 1476 | RandomMiiData4{ |
| 1389 | .gender = Gender::Male, | 1477 | .gender = static_cast<u32>(Gender::Male), |
| 1390 | .age = Age::Old, | 1478 | .age = static_cast<u32>(Age::Old), |
| 1391 | .race = Race::White, | 1479 | .race = static_cast<u32>(Race::White), |
| 1392 | .values_count = 15, | 1480 | .values_count = 15, |
| 1393 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, | 1481 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, |
| 1394 | }, | 1482 | }, |
| 1395 | Service::Mii::RandomMiiData4{ | 1483 | RandomMiiData4{ |
| 1396 | .gender = Gender::Male, | 1484 | .gender = static_cast<u32>(Gender::Male), |
| 1397 | .age = Age::Young, | 1485 | .age = static_cast<u32>(Age::Young), |
| 1398 | .race = Race::Asian, | 1486 | .race = static_cast<u32>(Race::Asian), |
| 1399 | .values_count = 18, | 1487 | .values_count = 18, |
| 1400 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1488 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1401 | }, | 1489 | }, |
| 1402 | Service::Mii::RandomMiiData4{ | 1490 | RandomMiiData4{ |
| 1403 | .gender = Gender::Male, | 1491 | .gender = static_cast<u32>(Gender::Male), |
| 1404 | .age = Age::Normal, | 1492 | .age = static_cast<u32>(Age::Normal), |
| 1405 | .race = Race::Asian, | 1493 | .race = static_cast<u32>(Race::Asian), |
| 1406 | .values_count = 18, | 1494 | .values_count = 18, |
| 1407 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1495 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1408 | }, | 1496 | }, |
| 1409 | Service::Mii::RandomMiiData4{ | 1497 | RandomMiiData4{ |
| 1410 | .gender = Gender::Male, | 1498 | .gender = static_cast<u32>(Gender::Male), |
| 1411 | .age = Age::Old, | 1499 | .age = static_cast<u32>(Age::Old), |
| 1412 | .race = Race::Asian, | 1500 | .race = static_cast<u32>(Race::Asian), |
| 1413 | .values_count = 15, | 1501 | .values_count = 15, |
| 1414 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, | 1502 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, |
| 1415 | }, | 1503 | }, |
| 1416 | Service::Mii::RandomMiiData4{ | 1504 | RandomMiiData4{ |
| 1417 | .gender = Gender::Female, | 1505 | .gender = static_cast<u32>(Gender::Female), |
| 1418 | .age = Age::Young, | 1506 | .age = static_cast<u32>(Age::Young), |
| 1419 | .race = Race::Black, | 1507 | .race = static_cast<u32>(Race::Black), |
| 1420 | .values_count = 8, | 1508 | .values_count = 8, |
| 1421 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, | 1509 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, |
| 1422 | }, | 1510 | }, |
| 1423 | Service::Mii::RandomMiiData4{ | 1511 | RandomMiiData4{ |
| 1424 | .gender = Gender::Female, | 1512 | .gender = static_cast<u32>(Gender::Female), |
| 1425 | .age = Age::Normal, | 1513 | .age = static_cast<u32>(Age::Normal), |
| 1426 | .race = Race::Black, | 1514 | .race = static_cast<u32>(Race::Black), |
| 1427 | .values_count = 8, | 1515 | .values_count = 8, |
| 1428 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, | 1516 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, |
| 1429 | }, | 1517 | }, |
| 1430 | Service::Mii::RandomMiiData4{ | 1518 | RandomMiiData4{ |
| 1431 | .gender = Gender::Female, | 1519 | .gender = static_cast<u32>(Gender::Female), |
| 1432 | .age = Age::Old, | 1520 | .age = static_cast<u32>(Age::Old), |
| 1433 | .race = Race::Black, | 1521 | .race = static_cast<u32>(Race::Black), |
| 1434 | .values_count = 8, | 1522 | .values_count = 8, |
| 1435 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, | 1523 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, |
| 1436 | }, | 1524 | }, |
| 1437 | Service::Mii::RandomMiiData4{ | 1525 | RandomMiiData4{ |
| 1438 | .gender = Gender::Female, | 1526 | .gender = static_cast<u32>(Gender::Female), |
| 1439 | .age = Age::Young, | 1527 | .age = static_cast<u32>(Age::Young), |
| 1440 | .race = Race::White, | 1528 | .race = static_cast<u32>(Race::White), |
| 1441 | .values_count = 12, | 1529 | .values_count = 12, |
| 1442 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, | 1530 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, |
| 1443 | }, | 1531 | }, |
| 1444 | Service::Mii::RandomMiiData4{ | 1532 | RandomMiiData4{ |
| 1445 | .gender = Gender::Female, | 1533 | .gender = static_cast<u32>(Gender::Female), |
| 1446 | .age = Age::Normal, | 1534 | .age = static_cast<u32>(Age::Normal), |
| 1447 | .race = Race::White, | 1535 | .race = static_cast<u32>(Race::White), |
| 1448 | .values_count = 11, | 1536 | .values_count = 11, |
| 1449 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, | 1537 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, |
| 1450 | }, | 1538 | }, |
| 1451 | Service::Mii::RandomMiiData4{ | 1539 | RandomMiiData4{ |
| 1452 | .gender = Gender::Female, | 1540 | .gender = static_cast<u32>(Gender::Female), |
| 1453 | .age = Age::Old, | 1541 | .age = static_cast<u32>(Age::Old), |
| 1454 | .race = Race::White, | 1542 | .race = static_cast<u32>(Race::White), |
| 1455 | .values_count = 10, | 1543 | .values_count = 10, |
| 1456 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, | 1544 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, |
| 1457 | }, | 1545 | }, |
| 1458 | Service::Mii::RandomMiiData4{ | 1546 | RandomMiiData4{ |
| 1459 | .gender = Gender::Female, | 1547 | .gender = static_cast<u32>(Gender::Female), |
| 1460 | .age = Age::Young, | 1548 | .age = static_cast<u32>(Age::Young), |
| 1461 | .race = Race::Asian, | 1549 | .race = static_cast<u32>(Race::Asian), |
| 1462 | .values_count = 12, | 1550 | .values_count = 12, |
| 1463 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, | 1551 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, |
| 1464 | }, | 1552 | }, |
| 1465 | Service::Mii::RandomMiiData4{ | 1553 | RandomMiiData4{ |
| 1466 | .gender = Gender::Female, | 1554 | .gender = static_cast<u32>(Gender::Female), |
| 1467 | .age = Age::Normal, | 1555 | .age = static_cast<u32>(Age::Normal), |
| 1468 | .race = Race::Asian, | 1556 | .race = static_cast<u32>(Race::Asian), |
| 1469 | .values_count = 11, | 1557 | .values_count = 11, |
| 1470 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, | 1558 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, |
| 1471 | }, | 1559 | }, |
| 1472 | Service::Mii::RandomMiiData4{ | 1560 | RandomMiiData4{ |
| 1473 | .gender = Gender::Female, | 1561 | .gender = static_cast<u32>(Gender::Female), |
| 1474 | .age = Age::Old, | 1562 | .age = static_cast<u32>(Age::Old), |
| 1475 | .race = Race::Asian, | 1563 | .race = static_cast<u32>(Race::Asian), |
| 1476 | .values_count = 10, | 1564 | .values_count = 10, |
| 1477 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, | 1565 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, |
| 1478 | }, | 1566 | }, |
| 1479 | }; | 1567 | }; |
| 1480 | 1568 | ||
| 1481 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ | 1569 | const std::array<RandomMiiData4, 18> RandomMiiMouthType{ |
| 1482 | Service::Mii::RandomMiiData4{ | 1570 | RandomMiiData4{ |
| 1483 | .gender = Gender::Male, | 1571 | .gender = static_cast<u32>(Gender::Male), |
| 1484 | .age = Age::Young, | 1572 | .age = static_cast<u32>(Age::Young), |
| 1485 | .race = Race::Black, | 1573 | .race = static_cast<u32>(Race::Black), |
| 1486 | .values_count = 25, | 1574 | .values_count = 25, |
| 1487 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, | 1575 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, |
| 1488 | 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, | 1576 | 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, |
| 1489 | }, | 1577 | }, |
| 1490 | Service::Mii::RandomMiiData4{ | 1578 | RandomMiiData4{ |
| 1491 | .gender = Gender::Male, | 1579 | .gender = static_cast<u32>(Gender::Male), |
| 1492 | .age = Age::Normal, | 1580 | .age = static_cast<u32>(Age::Normal), |
| 1493 | .race = Race::Black, | 1581 | .race = static_cast<u32>(Race::Black), |
| 1494 | .values_count = 27, | 1582 | .values_count = 27, |
| 1495 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, | 1583 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, |
| 1496 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, | 1584 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, |
| 1497 | }, | 1585 | }, |
| 1498 | Service::Mii::RandomMiiData4{ | 1586 | RandomMiiData4{ |
| 1499 | .gender = Gender::Male, | 1587 | .gender = static_cast<u32>(Gender::Male), |
| 1500 | .age = Age::Old, | 1588 | .age = static_cast<u32>(Age::Old), |
| 1501 | .race = Race::Black, | 1589 | .race = static_cast<u32>(Race::Black), |
| 1502 | .values_count = 28, | 1590 | .values_count = 28, |
| 1503 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, | 1591 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, |
| 1504 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, | 1592 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, |
| 1505 | }, | 1593 | }, |
| 1506 | Service::Mii::RandomMiiData4{ | 1594 | RandomMiiData4{ |
| 1507 | .gender = Gender::Male, | 1595 | .gender = static_cast<u32>(Gender::Male), |
| 1508 | .age = Age::Young, | 1596 | .age = static_cast<u32>(Age::Young), |
| 1509 | .race = Race::White, | 1597 | .race = static_cast<u32>(Race::White), |
| 1510 | .values_count = 24, | 1598 | .values_count = 24, |
| 1511 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, | 1599 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, |
| 1512 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1600 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1513 | }, | 1601 | }, |
| 1514 | Service::Mii::RandomMiiData4{ | 1602 | RandomMiiData4{ |
| 1515 | .gender = Gender::Male, | 1603 | .gender = static_cast<u32>(Gender::Male), |
| 1516 | .age = Age::Normal, | 1604 | .age = static_cast<u32>(Age::Normal), |
| 1517 | .race = Race::White, | 1605 | .race = static_cast<u32>(Race::White), |
| 1518 | .values_count = 26, | 1606 | .values_count = 26, |
| 1519 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1607 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1520 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1608 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1521 | }, | 1609 | }, |
| 1522 | Service::Mii::RandomMiiData4{ | 1610 | RandomMiiData4{ |
| 1523 | .gender = Gender::Male, | 1611 | .gender = static_cast<u32>(Gender::Male), |
| 1524 | .age = Age::Old, | 1612 | .age = static_cast<u32>(Age::Old), |
| 1525 | .race = Race::White, | 1613 | .race = static_cast<u32>(Race::White), |
| 1526 | .values_count = 26, | 1614 | .values_count = 26, |
| 1527 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1615 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1528 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1616 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1529 | }, | 1617 | }, |
| 1530 | Service::Mii::RandomMiiData4{ | 1618 | RandomMiiData4{ |
| 1531 | .gender = Gender::Male, | 1619 | .gender = static_cast<u32>(Gender::Male), |
| 1532 | .age = Age::Young, | 1620 | .age = static_cast<u32>(Age::Young), |
| 1533 | .race = Race::Asian, | 1621 | .race = static_cast<u32>(Race::Asian), |
| 1534 | .values_count = 24, | 1622 | .values_count = 24, |
| 1535 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, | 1623 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, |
| 1536 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1624 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1537 | }, | 1625 | }, |
| 1538 | Service::Mii::RandomMiiData4{ | 1626 | RandomMiiData4{ |
| 1539 | .gender = Gender::Male, | 1627 | .gender = static_cast<u32>(Gender::Male), |
| 1540 | .age = Age::Normal, | 1628 | .age = static_cast<u32>(Age::Normal), |
| 1541 | .race = Race::Asian, | 1629 | .race = static_cast<u32>(Race::Asian), |
| 1542 | .values_count = 26, | 1630 | .values_count = 26, |
| 1543 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1631 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1544 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1632 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1545 | }, | 1633 | }, |
| 1546 | Service::Mii::RandomMiiData4{ | 1634 | RandomMiiData4{ |
| 1547 | .gender = Gender::Male, | 1635 | .gender = static_cast<u32>(Gender::Male), |
| 1548 | .age = Age::Old, | 1636 | .age = static_cast<u32>(Age::Old), |
| 1549 | .race = Race::Asian, | 1637 | .race = static_cast<u32>(Race::Asian), |
| 1550 | .values_count = 26, | 1638 | .values_count = 26, |
| 1551 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1639 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1552 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1640 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1553 | }, | 1641 | }, |
| 1554 | Service::Mii::RandomMiiData4{ | 1642 | RandomMiiData4{ |
| 1555 | .gender = Gender::Female, | 1643 | .gender = static_cast<u32>(Gender::Female), |
| 1556 | .age = Age::Young, | 1644 | .age = static_cast<u32>(Age::Young), |
| 1557 | .race = Race::Black, | 1645 | .race = static_cast<u32>(Race::Black), |
| 1558 | .values_count = 25, | 1646 | .values_count = 25, |
| 1559 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, | 1647 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, |
| 1560 | 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, | 1648 | 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, |
| 1561 | }, | 1649 | }, |
| 1562 | Service::Mii::RandomMiiData4{ | 1650 | RandomMiiData4{ |
| 1563 | .gender = Gender::Female, | 1651 | .gender = static_cast<u32>(Gender::Female), |
| 1564 | .age = Age::Normal, | 1652 | .age = static_cast<u32>(Age::Normal), |
| 1565 | .race = Race::Black, | 1653 | .race = static_cast<u32>(Race::Black), |
| 1566 | .values_count = 26, | 1654 | .values_count = 26, |
| 1567 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1655 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1568 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, | 1656 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, |
| 1569 | }, | 1657 | }, |
| 1570 | Service::Mii::RandomMiiData4{ | 1658 | RandomMiiData4{ |
| 1571 | .gender = Gender::Female, | 1659 | .gender = static_cast<u32>(Gender::Female), |
| 1572 | .age = Age::Old, | 1660 | .age = static_cast<u32>(Age::Old), |
| 1573 | .race = Race::Black, | 1661 | .race = static_cast<u32>(Race::Black), |
| 1574 | .values_count = 26, | 1662 | .values_count = 26, |
| 1575 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1663 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1576 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, | 1664 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, |
| 1577 | }, | 1665 | }, |
| 1578 | Service::Mii::RandomMiiData4{ | 1666 | RandomMiiData4{ |
| 1579 | .gender = Gender::Female, | 1667 | .gender = static_cast<u32>(Gender::Female), |
| 1580 | .age = Age::Young, | 1668 | .age = static_cast<u32>(Age::Young), |
| 1581 | .race = Race::White, | 1669 | .race = static_cast<u32>(Race::White), |
| 1582 | .values_count = 25, | 1670 | .values_count = 25, |
| 1583 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, | 1671 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, |
| 1584 | 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, | 1672 | 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, |
| 1585 | }, | 1673 | }, |
| 1586 | Service::Mii::RandomMiiData4{ | 1674 | RandomMiiData4{ |
| 1587 | .gender = Gender::Female, | 1675 | .gender = static_cast<u32>(Gender::Female), |
| 1588 | .age = Age::Normal, | 1676 | .age = static_cast<u32>(Age::Normal), |
| 1589 | .race = Race::White, | 1677 | .race = static_cast<u32>(Race::White), |
| 1590 | .values_count = 26, | 1678 | .values_count = 26, |
| 1591 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1679 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1592 | 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, | 1680 | 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, |
| 1593 | }, | 1681 | }, |
| 1594 | Service::Mii::RandomMiiData4{ | 1682 | RandomMiiData4{ |
| 1595 | .gender = Gender::Female, | 1683 | .gender = static_cast<u32>(Gender::Female), |
| 1596 | .age = Age::Old, | 1684 | .age = static_cast<u32>(Age::Old), |
| 1597 | .race = Race::White, | 1685 | .race = static_cast<u32>(Race::White), |
| 1598 | .values_count = 25, | 1686 | .values_count = 25, |
| 1599 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1687 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1600 | 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, | 1688 | 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, |
| 1601 | }, | 1689 | }, |
| 1602 | Service::Mii::RandomMiiData4{ | 1690 | RandomMiiData4{ |
| 1603 | .gender = Gender::Female, | 1691 | .gender = static_cast<u32>(Gender::Female), |
| 1604 | .age = Age::Young, | 1692 | .age = static_cast<u32>(Age::Young), |
| 1605 | .race = Race::Asian, | 1693 | .race = static_cast<u32>(Race::Asian), |
| 1606 | .values_count = 24, | 1694 | .values_count = 24, |
| 1607 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, | 1695 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, |
| 1608 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, | 1696 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, |
| 1609 | }, | 1697 | }, |
| 1610 | Service::Mii::RandomMiiData4{ | 1698 | RandomMiiData4{ |
| 1611 | .gender = Gender::Female, | 1699 | .gender = static_cast<u32>(Gender::Female), |
| 1612 | .age = Age::Normal, | 1700 | .age = static_cast<u32>(Age::Normal), |
| 1613 | .race = Race::Asian, | 1701 | .race = static_cast<u32>(Race::Asian), |
| 1614 | .values_count = 25, | 1702 | .values_count = 25, |
| 1615 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1703 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1616 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, | 1704 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, |
| 1617 | }, | 1705 | }, |
| 1618 | Service::Mii::RandomMiiData4{ | 1706 | RandomMiiData4{ |
| 1619 | .gender = Gender::Female, | 1707 | .gender = static_cast<u32>(Gender::Female), |
| 1620 | .age = Age::Old, | 1708 | .age = static_cast<u32>(Age::Old), |
| 1621 | .race = Race::Asian, | 1709 | .race = static_cast<u32>(Race::Asian), |
| 1622 | .values_count = 25, | 1710 | .values_count = 25, |
| 1623 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1711 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1624 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, | 1712 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, |
| 1625 | }, | 1713 | }, |
| 1626 | }; | 1714 | }; |
| 1627 | 1715 | ||
| 1628 | const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ | 1716 | const std::array<RandomMiiData2, 3> RandomMiiGlassType{ |
| 1629 | Service::Mii::RandomMiiData2{ | 1717 | RandomMiiData2{ |
| 1630 | .arg_1 = 0, | 1718 | .arg_1 = 0, |
| 1631 | .values_count = 9, | 1719 | .values_count = 9, |
| 1632 | .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, | 1720 | .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, |
| 1633 | }, | 1721 | }, |
| 1634 | Service::Mii::RandomMiiData2{ | 1722 | RandomMiiData2{ |
| 1635 | .arg_1 = 1, | 1723 | .arg_1 = 1, |
| 1636 | .values_count = 9, | 1724 | .values_count = 9, |
| 1637 | .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, | 1725 | .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, |
| 1638 | }, | 1726 | }, |
| 1639 | Service::Mii::RandomMiiData2{ | 1727 | RandomMiiData2{ |
| 1640 | .arg_1 = 2, | 1728 | .arg_1 = 2, |
| 1641 | .values_count = 9, | 1729 | .values_count = 9, |
| 1642 | .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, | 1730 | .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, |
| 1643 | }, | 1731 | }, |
| 1644 | }; | 1732 | }; |
| 1645 | 1733 | ||
| 1734 | u8 FromVer3GetFacelineColor(u8 color) { | ||
| 1735 | return FromVer3FacelineColorTable[color]; | ||
| 1736 | } | ||
| 1737 | |||
| 1738 | u8 FromVer3GetHairColor(u8 color) { | ||
| 1739 | return FromVer3HairColorTable[color]; | ||
| 1740 | } | ||
| 1741 | |||
| 1742 | u8 FromVer3GetEyeColor(u8 color) { | ||
| 1743 | return FromVer3EyeColorTable[color]; | ||
| 1744 | } | ||
| 1745 | |||
| 1746 | u8 FromVer3GetMouthlineColor(u8 color) { | ||
| 1747 | return FromVer3MouthlineColorTable[color]; | ||
| 1748 | } | ||
| 1749 | |||
| 1750 | u8 FromVer3GetGlassColor(u8 color) { | ||
| 1751 | return FromVer3GlassColorTable[color]; | ||
| 1752 | } | ||
| 1753 | |||
| 1754 | u8 FromVer3GetGlassType(u8 type) { | ||
| 1755 | return FromVer3GlassTypeTable[type]; | ||
| 1756 | } | ||
| 1757 | |||
| 1758 | FacelineColor GetFacelineColorFromVer3(u32 color) { | ||
| 1759 | return static_cast<FacelineColor>(Ver3FacelineColorTable[color]); | ||
| 1760 | } | ||
| 1761 | |||
| 1762 | CommonColor GetHairColorFromVer3(u32 color) { | ||
| 1763 | return static_cast<CommonColor>(Ver3HairColorTable[color]); | ||
| 1764 | } | ||
| 1765 | |||
| 1766 | CommonColor GetEyeColorFromVer3(u32 color) { | ||
| 1767 | return static_cast<CommonColor>(Ver3EyeColorTable[color]); | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | CommonColor GetMouthColorFromVer3(u32 color) { | ||
| 1771 | return static_cast<CommonColor>(Ver3MouthColorTable[color]); | ||
| 1772 | } | ||
| 1773 | |||
| 1774 | CommonColor GetGlassColorFromVer3(u32 color) { | ||
| 1775 | return static_cast<CommonColor>(Ver3GlassColorTable[color]); | ||
| 1776 | } | ||
| 1777 | |||
| 1646 | } // namespace Service::Mii::RawData | 1778 | } // namespace Service::Mii::RawData |
diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h new file mode 100644 index 000000000..9a4cfa738 --- /dev/null +++ b/src/core/hle/service/mii/types/raw_data.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include "core/hle/service/mii/mii_types.h" | ||
| 9 | |||
| 10 | namespace Service::Mii::RawData { | ||
| 11 | |||
| 12 | struct RandomMiiValues { | ||
| 13 | std::array<u8, 188> values{}; | ||
| 14 | }; | ||
| 15 | static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); | ||
| 16 | |||
| 17 | struct RandomMiiData4 { | ||
| 18 | u32 gender{}; | ||
| 19 | u32 age{}; | ||
| 20 | u32 race{}; | ||
| 21 | u32 values_count{}; | ||
| 22 | std::array<u32, 47> values{}; | ||
| 23 | }; | ||
| 24 | static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); | ||
| 25 | |||
| 26 | struct RandomMiiData3 { | ||
| 27 | u32 arg_1; | ||
| 28 | u32 arg_2; | ||
| 29 | u32 values_count; | ||
| 30 | std::array<u32, 47> values{}; | ||
| 31 | }; | ||
| 32 | static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); | ||
| 33 | |||
| 34 | struct RandomMiiData2 { | ||
| 35 | u32 arg_1; | ||
| 36 | u32 values_count; | ||
| 37 | std::array<u32, 47> values{}; | ||
| 38 | }; | ||
| 39 | static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); | ||
| 40 | |||
| 41 | extern const std::array<Service::Mii::DefaultMii, 2> BaseMii; | ||
| 42 | extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii; | ||
| 43 | |||
| 44 | extern const std::array<u8, 62> EyeRotateLookup; | ||
| 45 | extern const std::array<u8, 24> EyebrowRotateLookup; | ||
| 46 | |||
| 47 | extern const std::array<RandomMiiData4, 18> RandomMiiFaceline; | ||
| 48 | extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor; | ||
| 49 | extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle; | ||
| 50 | extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup; | ||
| 51 | extern const std::array<RandomMiiData4, 18> RandomMiiHairType; | ||
| 52 | extern const std::array<RandomMiiData3, 9> RandomMiiHairColor; | ||
| 53 | extern const std::array<RandomMiiData4, 18> RandomMiiEyeType; | ||
| 54 | extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor; | ||
| 55 | extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType; | ||
| 56 | extern const std::array<RandomMiiData4, 18> RandomMiiNoseType; | ||
| 57 | extern const std::array<RandomMiiData4, 18> RandomMiiMouthType; | ||
| 58 | extern const std::array<RandomMiiData2, 3> RandomMiiGlassType; | ||
| 59 | |||
| 60 | u8 FromVer3GetFacelineColor(u8 color); | ||
| 61 | u8 FromVer3GetHairColor(u8 color); | ||
| 62 | u8 FromVer3GetEyeColor(u8 color); | ||
| 63 | u8 FromVer3GetMouthlineColor(u8 color); | ||
| 64 | u8 FromVer3GetGlassColor(u8 color); | ||
| 65 | u8 FromVer3GetGlassType(u8 type); | ||
| 66 | |||
| 67 | FacelineColor GetFacelineColorFromVer3(u32 color); | ||
| 68 | CommonColor GetHairColorFromVer3(u32 color); | ||
| 69 | CommonColor GetEyeColorFromVer3(u32 color); | ||
| 70 | CommonColor GetMouthColorFromVer3(u32 color); | ||
| 71 | CommonColor GetGlassColorFromVer3(u32 color); | ||
| 72 | |||
| 73 | } // namespace Service::Mii::RawData | ||
diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp new file mode 100644 index 000000000..8fce636c7 --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.cpp | |||
| @@ -0,0 +1,643 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/mii_util.h" | ||
| 5 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 6 | #include "core/hle/service/mii/types/store_data.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | void StoreData::BuildDefault(u32 mii_index) { | ||
| 11 | const auto& default_mii = RawData::DefaultMii[mii_index]; | ||
| 12 | core_data.SetDefault(); | ||
| 13 | |||
| 14 | core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); | ||
| 15 | core_data.SetFacelineColor( | ||
| 16 | RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); | ||
| 17 | core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); | ||
| 18 | core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); | ||
| 19 | |||
| 20 | core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); | ||
| 21 | core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); | ||
| 22 | core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); | ||
| 23 | core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); | ||
| 24 | core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); | ||
| 25 | core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); | ||
| 26 | core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); | ||
| 27 | core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); | ||
| 28 | core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); | ||
| 29 | core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); | ||
| 30 | |||
| 31 | core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); | ||
| 32 | core_data.SetEyebrowColor( | ||
| 33 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); | ||
| 34 | core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); | ||
| 35 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); | ||
| 36 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); | ||
| 37 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); | ||
| 38 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); | ||
| 39 | |||
| 40 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); | ||
| 41 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); | ||
| 42 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); | ||
| 43 | |||
| 44 | core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); | ||
| 45 | core_data.SetMouthColor( | ||
| 46 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); | ||
| 47 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); | ||
| 48 | core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); | ||
| 49 | core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); | ||
| 50 | |||
| 51 | core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); | ||
| 52 | core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); | ||
| 53 | core_data.SetBeardColor( | ||
| 54 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); | ||
| 55 | core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); | ||
| 56 | core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); | ||
| 57 | |||
| 58 | core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); | ||
| 59 | core_data.SetGlassColor( | ||
| 60 | RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); | ||
| 61 | core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); | ||
| 62 | core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); | ||
| 63 | |||
| 64 | core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); | ||
| 65 | core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); | ||
| 66 | core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); | ||
| 67 | core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); | ||
| 68 | |||
| 69 | core_data.SetHeight(static_cast<u8>(default_mii.height)); | ||
| 70 | core_data.SetBuild(static_cast<u8>(default_mii.weight)); | ||
| 71 | core_data.SetGender(static_cast<Gender>(default_mii.gender)); | ||
| 72 | core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); | ||
| 73 | core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); | ||
| 74 | core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); | ||
| 75 | core_data.SetType(static_cast<u8>(default_mii.type)); | ||
| 76 | core_data.SetNickname(default_mii.nickname); | ||
| 77 | |||
| 78 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 79 | create_id = MiiUtil::MakeCreateId(); | ||
| 80 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 81 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 82 | } | ||
| 83 | |||
| 84 | void StoreData::BuildBase(Gender gender) { | ||
| 85 | const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0]; | ||
| 86 | core_data.SetDefault(); | ||
| 87 | |||
| 88 | core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); | ||
| 89 | core_data.SetFacelineColor( | ||
| 90 | RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); | ||
| 91 | core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); | ||
| 92 | core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); | ||
| 93 | |||
| 94 | core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); | ||
| 95 | core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); | ||
| 96 | core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); | ||
| 97 | core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); | ||
| 98 | core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); | ||
| 99 | core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); | ||
| 100 | core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); | ||
| 101 | core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); | ||
| 102 | core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); | ||
| 103 | core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); | ||
| 104 | |||
| 105 | core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); | ||
| 106 | core_data.SetEyebrowColor( | ||
| 107 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); | ||
| 108 | core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); | ||
| 109 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); | ||
| 110 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); | ||
| 111 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); | ||
| 112 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); | ||
| 113 | |||
| 114 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); | ||
| 115 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); | ||
| 116 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); | ||
| 117 | |||
| 118 | core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); | ||
| 119 | core_data.SetMouthColor( | ||
| 120 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); | ||
| 121 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); | ||
| 122 | core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); | ||
| 123 | core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); | ||
| 124 | |||
| 125 | core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); | ||
| 126 | core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); | ||
| 127 | core_data.SetBeardColor( | ||
| 128 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); | ||
| 129 | core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); | ||
| 130 | core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); | ||
| 131 | |||
| 132 | core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); | ||
| 133 | core_data.SetGlassColor( | ||
| 134 | RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); | ||
| 135 | core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); | ||
| 136 | core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); | ||
| 137 | |||
| 138 | core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); | ||
| 139 | core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); | ||
| 140 | core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); | ||
| 141 | core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); | ||
| 142 | |||
| 143 | core_data.SetHeight(static_cast<u8>(default_mii.height)); | ||
| 144 | core_data.SetBuild(static_cast<u8>(default_mii.weight)); | ||
| 145 | core_data.SetGender(static_cast<Gender>(default_mii.gender)); | ||
| 146 | core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); | ||
| 147 | core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); | ||
| 148 | core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); | ||
| 149 | core_data.SetType(static_cast<u8>(default_mii.type)); | ||
| 150 | core_data.SetNickname(default_mii.nickname); | ||
| 151 | |||
| 152 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 153 | create_id = MiiUtil::MakeCreateId(); | ||
| 154 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 155 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 156 | } | ||
| 157 | |||
| 158 | void StoreData::BuildRandom(Age age, Gender gender, Race race) { | ||
| 159 | core_data.BuildRandom(age, gender, race); | ||
| 160 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 161 | create_id = MiiUtil::MakeCreateId(); | ||
| 162 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 163 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 164 | } | ||
| 165 | |||
| 166 | void StoreData::SetInvalidName() { | ||
| 167 | const auto& invalid_name = core_data.GetInvalidNickname(); | ||
| 168 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 169 | core_data.SetNickname(invalid_name); | ||
| 170 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 171 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 172 | } | ||
| 173 | |||
| 174 | bool StoreData::IsSpecial() const { | ||
| 175 | return GetType() == 1; | ||
| 176 | } | ||
| 177 | |||
| 178 | u32 StoreData::IsValid() const { | ||
| 179 | // TODO: complete this | ||
| 180 | return 0; | ||
| 181 | } | ||
| 182 | |||
| 183 | void StoreData::SetFontRegion(FontRegion value) { | ||
| 184 | core_data.SetFontRegion(value); | ||
| 185 | } | ||
| 186 | |||
| 187 | void StoreData::SetFavoriteColor(FavoriteColor value) { | ||
| 188 | core_data.SetFavoriteColor(value); | ||
| 189 | } | ||
| 190 | |||
| 191 | void StoreData::SetGender(Gender value) { | ||
| 192 | core_data.SetGender(value); | ||
| 193 | } | ||
| 194 | |||
| 195 | void StoreData::SetHeight(u8 value) { | ||
| 196 | core_data.SetHeight(value); | ||
| 197 | } | ||
| 198 | |||
| 199 | void StoreData::SetBuild(u8 value) { | ||
| 200 | core_data.SetBuild(value); | ||
| 201 | } | ||
| 202 | |||
| 203 | void StoreData::SetType(u8 value) { | ||
| 204 | core_data.SetType(value); | ||
| 205 | } | ||
| 206 | |||
| 207 | void StoreData::SetRegionMove(u8 value) { | ||
| 208 | core_data.SetRegionMove(value); | ||
| 209 | } | ||
| 210 | |||
| 211 | void StoreData::SetFacelineType(FacelineType value) { | ||
| 212 | core_data.SetFacelineType(value); | ||
| 213 | } | ||
| 214 | |||
| 215 | void StoreData::SetFacelineColor(FacelineColor value) { | ||
| 216 | core_data.SetFacelineColor(value); | ||
| 217 | } | ||
| 218 | |||
| 219 | void StoreData::SetFacelineWrinkle(FacelineWrinkle value) { | ||
| 220 | core_data.SetFacelineWrinkle(value); | ||
| 221 | } | ||
| 222 | |||
| 223 | void StoreData::SetFacelineMake(FacelineMake value) { | ||
| 224 | core_data.SetFacelineMake(value); | ||
| 225 | } | ||
| 226 | |||
| 227 | void StoreData::SetHairType(HairType value) { | ||
| 228 | core_data.SetHairType(value); | ||
| 229 | } | ||
| 230 | |||
| 231 | void StoreData::SetHairColor(CommonColor value) { | ||
| 232 | core_data.SetHairColor(value); | ||
| 233 | } | ||
| 234 | |||
| 235 | void StoreData::SetHairFlip(HairFlip value) { | ||
| 236 | core_data.SetHairFlip(value); | ||
| 237 | } | ||
| 238 | |||
| 239 | void StoreData::SetEyeType(EyeType value) { | ||
| 240 | core_data.SetEyeType(value); | ||
| 241 | } | ||
| 242 | |||
| 243 | void StoreData::SetEyeColor(CommonColor value) { | ||
| 244 | core_data.SetEyeColor(value); | ||
| 245 | } | ||
| 246 | |||
| 247 | void StoreData::SetEyeScale(u8 value) { | ||
| 248 | core_data.SetEyeScale(value); | ||
| 249 | } | ||
| 250 | |||
| 251 | void StoreData::SetEyeAspect(u8 value) { | ||
| 252 | core_data.SetEyeAspect(value); | ||
| 253 | } | ||
| 254 | |||
| 255 | void StoreData::SetEyeRotate(u8 value) { | ||
| 256 | core_data.SetEyeRotate(value); | ||
| 257 | } | ||
| 258 | |||
| 259 | void StoreData::SetEyeX(u8 value) { | ||
| 260 | core_data.SetEyeX(value); | ||
| 261 | } | ||
| 262 | |||
| 263 | void StoreData::SetEyeY(u8 value) { | ||
| 264 | core_data.SetEyeY(value); | ||
| 265 | } | ||
| 266 | |||
| 267 | void StoreData::SetEyebrowType(EyebrowType value) { | ||
| 268 | core_data.SetEyebrowType(value); | ||
| 269 | } | ||
| 270 | |||
| 271 | void StoreData::SetEyebrowColor(CommonColor value) { | ||
| 272 | core_data.SetEyebrowColor(value); | ||
| 273 | } | ||
| 274 | |||
| 275 | void StoreData::SetEyebrowScale(u8 value) { | ||
| 276 | core_data.SetEyebrowScale(value); | ||
| 277 | } | ||
| 278 | |||
| 279 | void StoreData::SetEyebrowAspect(u8 value) { | ||
| 280 | core_data.SetEyebrowAspect(value); | ||
| 281 | } | ||
| 282 | |||
| 283 | void StoreData::SetEyebrowRotate(u8 value) { | ||
| 284 | core_data.SetEyebrowRotate(value); | ||
| 285 | } | ||
| 286 | |||
| 287 | void StoreData::SetEyebrowX(u8 value) { | ||
| 288 | core_data.SetEyebrowX(value); | ||
| 289 | } | ||
| 290 | |||
| 291 | void StoreData::SetEyebrowY(u8 value) { | ||
| 292 | core_data.SetEyebrowY(value); | ||
| 293 | } | ||
| 294 | |||
| 295 | void StoreData::SetNoseType(NoseType value) { | ||
| 296 | core_data.SetNoseType(value); | ||
| 297 | } | ||
| 298 | |||
| 299 | void StoreData::SetNoseScale(u8 value) { | ||
| 300 | core_data.SetNoseScale(value); | ||
| 301 | } | ||
| 302 | |||
| 303 | void StoreData::SetNoseY(u8 value) { | ||
| 304 | core_data.SetNoseY(value); | ||
| 305 | } | ||
| 306 | |||
| 307 | void StoreData::SetMouthType(u8 value) { | ||
| 308 | core_data.SetMouthType(value); | ||
| 309 | } | ||
| 310 | |||
| 311 | void StoreData::SetMouthColor(CommonColor value) { | ||
| 312 | core_data.SetMouthColor(value); | ||
| 313 | } | ||
| 314 | |||
| 315 | void StoreData::SetMouthScale(u8 value) { | ||
| 316 | core_data.SetMouthScale(value); | ||
| 317 | } | ||
| 318 | |||
| 319 | void StoreData::SetMouthAspect(u8 value) { | ||
| 320 | core_data.SetMouthAspect(value); | ||
| 321 | } | ||
| 322 | |||
| 323 | void StoreData::SetMouthY(u8 value) { | ||
| 324 | core_data.SetMouthY(value); | ||
| 325 | } | ||
| 326 | |||
| 327 | void StoreData::SetBeardColor(CommonColor value) { | ||
| 328 | core_data.SetBeardColor(value); | ||
| 329 | } | ||
| 330 | |||
| 331 | void StoreData::SetBeardType(BeardType value) { | ||
| 332 | core_data.SetBeardType(value); | ||
| 333 | } | ||
| 334 | |||
| 335 | void StoreData::SetMustacheType(MustacheType value) { | ||
| 336 | core_data.SetMustacheType(value); | ||
| 337 | } | ||
| 338 | |||
| 339 | void StoreData::SetMustacheScale(u8 value) { | ||
| 340 | core_data.SetMustacheScale(value); | ||
| 341 | } | ||
| 342 | |||
| 343 | void StoreData::SetMustacheY(u8 value) { | ||
| 344 | core_data.SetMustacheY(value); | ||
| 345 | } | ||
| 346 | |||
| 347 | void StoreData::SetGlassType(GlassType value) { | ||
| 348 | core_data.SetGlassType(value); | ||
| 349 | } | ||
| 350 | |||
| 351 | void StoreData::SetGlassColor(CommonColor value) { | ||
| 352 | core_data.SetGlassColor(value); | ||
| 353 | } | ||
| 354 | |||
| 355 | void StoreData::SetGlassScale(u8 value) { | ||
| 356 | core_data.SetGlassScale(value); | ||
| 357 | } | ||
| 358 | |||
| 359 | void StoreData::SetGlassY(u8 value) { | ||
| 360 | core_data.SetGlassY(value); | ||
| 361 | } | ||
| 362 | |||
| 363 | void StoreData::SetMoleType(MoleType value) { | ||
| 364 | core_data.SetMoleType(value); | ||
| 365 | } | ||
| 366 | |||
| 367 | void StoreData::SetMoleScale(u8 value) { | ||
| 368 | core_data.SetMoleScale(value); | ||
| 369 | } | ||
| 370 | |||
| 371 | void StoreData::SetMoleX(u8 value) { | ||
| 372 | core_data.SetMoleX(value); | ||
| 373 | } | ||
| 374 | |||
| 375 | void StoreData::SetMoleY(u8 value) { | ||
| 376 | core_data.SetMoleY(value); | ||
| 377 | } | ||
| 378 | |||
| 379 | void StoreData::SetNickname(Nickname value) { | ||
| 380 | core_data.SetNickname(value); | ||
| 381 | } | ||
| 382 | |||
| 383 | Common::UUID StoreData::GetCreateId() const { | ||
| 384 | return create_id; | ||
| 385 | } | ||
| 386 | |||
| 387 | FontRegion StoreData::GetFontRegion() const { | ||
| 388 | return static_cast<FontRegion>(core_data.GetFontRegion()); | ||
| 389 | } | ||
| 390 | |||
| 391 | FavoriteColor StoreData::GetFavoriteColor() const { | ||
| 392 | return core_data.GetFavoriteColor(); | ||
| 393 | } | ||
| 394 | |||
| 395 | Gender StoreData::GetGender() const { | ||
| 396 | return core_data.GetGender(); | ||
| 397 | } | ||
| 398 | |||
| 399 | u8 StoreData::GetHeight() const { | ||
| 400 | return core_data.GetHeight(); | ||
| 401 | } | ||
| 402 | |||
| 403 | u8 StoreData::GetBuild() const { | ||
| 404 | return core_data.GetBuild(); | ||
| 405 | } | ||
| 406 | |||
| 407 | u8 StoreData::GetType() const { | ||
| 408 | return core_data.GetType(); | ||
| 409 | } | ||
| 410 | |||
| 411 | u8 StoreData::GetRegionMove() const { | ||
| 412 | return core_data.GetRegionMove(); | ||
| 413 | } | ||
| 414 | |||
| 415 | FacelineType StoreData::GetFacelineType() const { | ||
| 416 | return core_data.GetFacelineType(); | ||
| 417 | } | ||
| 418 | |||
| 419 | FacelineColor StoreData::GetFacelineColor() const { | ||
| 420 | return core_data.GetFacelineColor(); | ||
| 421 | } | ||
| 422 | |||
| 423 | FacelineWrinkle StoreData::GetFacelineWrinkle() const { | ||
| 424 | return core_data.GetFacelineWrinkle(); | ||
| 425 | } | ||
| 426 | |||
| 427 | FacelineMake StoreData::GetFacelineMake() const { | ||
| 428 | return core_data.GetFacelineMake(); | ||
| 429 | } | ||
| 430 | |||
| 431 | HairType StoreData::GetHairType() const { | ||
| 432 | return core_data.GetHairType(); | ||
| 433 | } | ||
| 434 | |||
| 435 | CommonColor StoreData::GetHairColor() const { | ||
| 436 | return core_data.GetHairColor(); | ||
| 437 | } | ||
| 438 | |||
| 439 | HairFlip StoreData::GetHairFlip() const { | ||
| 440 | return core_data.GetHairFlip(); | ||
| 441 | } | ||
| 442 | |||
| 443 | EyeType StoreData::GetEyeType() const { | ||
| 444 | return core_data.GetEyeType(); | ||
| 445 | } | ||
| 446 | |||
| 447 | CommonColor StoreData::GetEyeColor() const { | ||
| 448 | return core_data.GetEyeColor(); | ||
| 449 | } | ||
| 450 | |||
| 451 | u8 StoreData::GetEyeScale() const { | ||
| 452 | return core_data.GetEyeScale(); | ||
| 453 | } | ||
| 454 | |||
| 455 | u8 StoreData::GetEyeAspect() const { | ||
| 456 | return core_data.GetEyeAspect(); | ||
| 457 | } | ||
| 458 | |||
| 459 | u8 StoreData::GetEyeRotate() const { | ||
| 460 | return core_data.GetEyeRotate(); | ||
| 461 | } | ||
| 462 | |||
| 463 | u8 StoreData::GetEyeX() const { | ||
| 464 | return core_data.GetEyeX(); | ||
| 465 | } | ||
| 466 | |||
| 467 | u8 StoreData::GetEyeY() const { | ||
| 468 | return core_data.GetEyeY(); | ||
| 469 | } | ||
| 470 | |||
| 471 | EyebrowType StoreData::GetEyebrowType() const { | ||
| 472 | return core_data.GetEyebrowType(); | ||
| 473 | } | ||
| 474 | |||
| 475 | CommonColor StoreData::GetEyebrowColor() const { | ||
| 476 | return core_data.GetEyebrowColor(); | ||
| 477 | } | ||
| 478 | |||
| 479 | u8 StoreData::GetEyebrowScale() const { | ||
| 480 | return core_data.GetEyebrowScale(); | ||
| 481 | } | ||
| 482 | |||
| 483 | u8 StoreData::GetEyebrowAspect() const { | ||
| 484 | return core_data.GetEyebrowAspect(); | ||
| 485 | } | ||
| 486 | |||
| 487 | u8 StoreData::GetEyebrowRotate() const { | ||
| 488 | return core_data.GetEyebrowRotate(); | ||
| 489 | } | ||
| 490 | |||
| 491 | u8 StoreData::GetEyebrowX() const { | ||
| 492 | return core_data.GetEyebrowX(); | ||
| 493 | } | ||
| 494 | |||
| 495 | u8 StoreData::GetEyebrowY() const { | ||
| 496 | return core_data.GetEyebrowY(); | ||
| 497 | } | ||
| 498 | |||
| 499 | NoseType StoreData::GetNoseType() const { | ||
| 500 | return core_data.GetNoseType(); | ||
| 501 | } | ||
| 502 | |||
| 503 | u8 StoreData::GetNoseScale() const { | ||
| 504 | return core_data.GetNoseScale(); | ||
| 505 | } | ||
| 506 | |||
| 507 | u8 StoreData::GetNoseY() const { | ||
| 508 | return core_data.GetNoseY(); | ||
| 509 | } | ||
| 510 | |||
| 511 | MouthType StoreData::GetMouthType() const { | ||
| 512 | return core_data.GetMouthType(); | ||
| 513 | } | ||
| 514 | |||
| 515 | CommonColor StoreData::GetMouthColor() const { | ||
| 516 | return core_data.GetMouthColor(); | ||
| 517 | } | ||
| 518 | |||
| 519 | u8 StoreData::GetMouthScale() const { | ||
| 520 | return core_data.GetMouthScale(); | ||
| 521 | } | ||
| 522 | |||
| 523 | u8 StoreData::GetMouthAspect() const { | ||
| 524 | return core_data.GetMouthAspect(); | ||
| 525 | } | ||
| 526 | |||
| 527 | u8 StoreData::GetMouthY() const { | ||
| 528 | return core_data.GetMouthY(); | ||
| 529 | } | ||
| 530 | |||
| 531 | CommonColor StoreData::GetBeardColor() const { | ||
| 532 | return core_data.GetBeardColor(); | ||
| 533 | } | ||
| 534 | |||
| 535 | BeardType StoreData::GetBeardType() const { | ||
| 536 | return core_data.GetBeardType(); | ||
| 537 | } | ||
| 538 | |||
| 539 | MustacheType StoreData::GetMustacheType() const { | ||
| 540 | return core_data.GetMustacheType(); | ||
| 541 | } | ||
| 542 | |||
| 543 | u8 StoreData::GetMustacheScale() const { | ||
| 544 | return core_data.GetMustacheScale(); | ||
| 545 | } | ||
| 546 | |||
| 547 | u8 StoreData::GetMustacheY() const { | ||
| 548 | return core_data.GetMustacheY(); | ||
| 549 | } | ||
| 550 | |||
| 551 | GlassType StoreData::GetGlassType() const { | ||
| 552 | return core_data.GetGlassType(); | ||
| 553 | } | ||
| 554 | |||
| 555 | CommonColor StoreData::GetGlassColor() const { | ||
| 556 | return core_data.GetGlassColor(); | ||
| 557 | } | ||
| 558 | |||
| 559 | u8 StoreData::GetGlassScale() const { | ||
| 560 | return core_data.GetGlassScale(); | ||
| 561 | } | ||
| 562 | |||
| 563 | u8 StoreData::GetGlassY() const { | ||
| 564 | return core_data.GetGlassY(); | ||
| 565 | } | ||
| 566 | |||
| 567 | MoleType StoreData::GetMoleType() const { | ||
| 568 | return core_data.GetMoleType(); | ||
| 569 | } | ||
| 570 | |||
| 571 | u8 StoreData::GetMoleScale() const { | ||
| 572 | return core_data.GetMoleScale(); | ||
| 573 | } | ||
| 574 | |||
| 575 | u8 StoreData::GetMoleX() const { | ||
| 576 | return core_data.GetMoleX(); | ||
| 577 | } | ||
| 578 | |||
| 579 | u8 StoreData::GetMoleY() const { | ||
| 580 | return core_data.GetMoleY(); | ||
| 581 | } | ||
| 582 | |||
| 583 | Nickname StoreData::GetNickname() const { | ||
| 584 | return core_data.GetNickname(); | ||
| 585 | } | ||
| 586 | |||
| 587 | bool StoreData::operator==(const StoreData& data) { | ||
| 588 | bool is_identical = data.core_data.IsValid() == 0; | ||
| 589 | is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; | ||
| 590 | is_identical &= GetCreateId() == data.GetCreateId(); | ||
| 591 | is_identical &= GetFontRegion() == data.GetFontRegion(); | ||
| 592 | is_identical &= GetFavoriteColor() == data.GetFavoriteColor(); | ||
| 593 | is_identical &= GetGender() == data.GetGender(); | ||
| 594 | is_identical &= GetHeight() == data.GetHeight(); | ||
| 595 | is_identical &= GetBuild() == data.GetBuild(); | ||
| 596 | is_identical &= GetType() == data.GetType(); | ||
| 597 | is_identical &= GetRegionMove() == data.GetRegionMove(); | ||
| 598 | is_identical &= GetFacelineType() == data.GetFacelineType(); | ||
| 599 | is_identical &= GetFacelineColor() == data.GetFacelineColor(); | ||
| 600 | is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle(); | ||
| 601 | is_identical &= GetFacelineMake() == data.GetFacelineMake(); | ||
| 602 | is_identical &= GetHairType() == data.GetHairType(); | ||
| 603 | is_identical &= GetHairColor() == data.GetHairColor(); | ||
| 604 | is_identical &= GetHairFlip() == data.GetHairFlip(); | ||
| 605 | is_identical &= GetEyeType() == data.GetEyeType(); | ||
| 606 | is_identical &= GetEyeColor() == data.GetEyeColor(); | ||
| 607 | is_identical &= GetEyeScale() == data.GetEyeScale(); | ||
| 608 | is_identical &= GetEyeAspect() == data.GetEyeAspect(); | ||
| 609 | is_identical &= GetEyeRotate() == data.GetEyeRotate(); | ||
| 610 | is_identical &= GetEyeX() == data.GetEyeX(); | ||
| 611 | is_identical &= GetEyeY() == data.GetEyeY(); | ||
| 612 | is_identical &= GetEyebrowType() == data.GetEyebrowType(); | ||
| 613 | is_identical &= GetEyebrowColor() == data.GetEyebrowColor(); | ||
| 614 | is_identical &= GetEyebrowScale() == data.GetEyebrowScale(); | ||
| 615 | is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect(); | ||
| 616 | is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate(); | ||
| 617 | is_identical &= GetEyebrowX() == data.GetEyebrowX(); | ||
| 618 | is_identical &= GetEyebrowY() == data.GetEyebrowY(); | ||
| 619 | is_identical &= GetNoseType() == data.GetNoseType(); | ||
| 620 | is_identical &= GetNoseScale() == data.GetNoseScale(); | ||
| 621 | is_identical &= GetNoseY() == data.GetNoseY(); | ||
| 622 | is_identical &= GetMouthType() == data.GetMouthType(); | ||
| 623 | is_identical &= GetMouthColor() == data.GetMouthColor(); | ||
| 624 | is_identical &= GetMouthScale() == data.GetMouthScale(); | ||
| 625 | is_identical &= GetMouthAspect() == data.GetMouthAspect(); | ||
| 626 | is_identical &= GetMouthY() == data.GetMouthY(); | ||
| 627 | is_identical &= GetBeardColor() == data.GetBeardColor(); | ||
| 628 | is_identical &= GetBeardType() == data.GetBeardType(); | ||
| 629 | is_identical &= GetMustacheType() == data.GetMustacheType(); | ||
| 630 | is_identical &= GetMustacheScale() == data.GetMustacheScale(); | ||
| 631 | is_identical &= GetMustacheY() == data.GetMustacheY(); | ||
| 632 | is_identical &= GetGlassType() == data.GetGlassType(); | ||
| 633 | is_identical &= GetGlassColor() == data.GetGlassColor(); | ||
| 634 | is_identical &= GetGlassScale() == data.GetGlassScale(); | ||
| 635 | is_identical &= GetGlassY() == data.GetGlassY(); | ||
| 636 | is_identical &= GetMoleType() == data.GetMoleType(); | ||
| 637 | is_identical &= GetMoleScale() == data.GetMoleScale(); | ||
| 638 | is_identical &= GetMoleX() == data.GetMoleX(); | ||
| 639 | is_identical &= data.GetMoleY() == data.GetMoleY(); | ||
| 640 | return is_identical; | ||
| 641 | } | ||
| 642 | |||
| 643 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h new file mode 100644 index 000000000..224c32cf8 --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.h | |||
| @@ -0,0 +1,145 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | #include "core/hle/service/mii/types/core_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | class StoreData { | ||
| 12 | public: | ||
| 13 | // nn::mii::detail::StoreDataRaw::BuildDefault | ||
| 14 | void BuildDefault(u32 mii_index); | ||
| 15 | // nn::mii::detail::StoreDataRaw::BuildDefault | ||
| 16 | |||
| 17 | void BuildBase(Gender gender); | ||
| 18 | // nn::mii::detail::StoreDataRaw::BuildRandom | ||
| 19 | void BuildRandom(Age age, Gender gender, Race race); | ||
| 20 | |||
| 21 | bool IsSpecial() const; | ||
| 22 | |||
| 23 | u32 IsValid() const; | ||
| 24 | |||
| 25 | void SetFontRegion(FontRegion value); | ||
| 26 | void SetFavoriteColor(FavoriteColor value); | ||
| 27 | void SetGender(Gender value); | ||
| 28 | void SetHeight(u8 value); | ||
| 29 | void SetBuild(u8 value); | ||
| 30 | void SetType(u8 value); | ||
| 31 | void SetRegionMove(u8 value); | ||
| 32 | void SetFacelineType(FacelineType value); | ||
| 33 | void SetFacelineColor(FacelineColor value); | ||
| 34 | void SetFacelineWrinkle(FacelineWrinkle value); | ||
| 35 | void SetFacelineMake(FacelineMake value); | ||
| 36 | void SetHairType(HairType value); | ||
| 37 | void SetHairColor(CommonColor value); | ||
| 38 | void SetHairFlip(HairFlip value); | ||
| 39 | void SetEyeType(EyeType value); | ||
| 40 | void SetEyeColor(CommonColor value); | ||
| 41 | void SetEyeScale(u8 value); | ||
| 42 | void SetEyeAspect(u8 value); | ||
| 43 | void SetEyeRotate(u8 value); | ||
| 44 | void SetEyeX(u8 value); | ||
| 45 | void SetEyeY(u8 value); | ||
| 46 | void SetEyebrowType(EyebrowType value); | ||
| 47 | void SetEyebrowColor(CommonColor value); | ||
| 48 | void SetEyebrowScale(u8 value); | ||
| 49 | void SetEyebrowAspect(u8 value); | ||
| 50 | void SetEyebrowRotate(u8 value); | ||
| 51 | void SetEyebrowX(u8 value); | ||
| 52 | void SetEyebrowY(u8 value); | ||
| 53 | void SetNoseType(NoseType value); | ||
| 54 | void SetNoseScale(u8 value); | ||
| 55 | void SetNoseY(u8 value); | ||
| 56 | void SetMouthType(u8 value); | ||
| 57 | void SetMouthColor(CommonColor value); | ||
| 58 | void SetMouthScale(u8 value); | ||
| 59 | void SetMouthAspect(u8 value); | ||
| 60 | void SetMouthY(u8 value); | ||
| 61 | void SetBeardColor(CommonColor value); | ||
| 62 | void SetBeardType(BeardType value); | ||
| 63 | void SetMustacheType(MustacheType value); | ||
| 64 | void SetMustacheScale(u8 value); | ||
| 65 | void SetMustacheY(u8 value); | ||
| 66 | void SetGlassType(GlassType value); | ||
| 67 | void SetGlassColor(CommonColor value); | ||
| 68 | void SetGlassScale(u8 value); | ||
| 69 | void SetGlassY(u8 value); | ||
| 70 | void SetMoleType(MoleType value); | ||
| 71 | void SetMoleScale(u8 value); | ||
| 72 | void SetMoleX(u8 value); | ||
| 73 | void SetMoleY(u8 value); | ||
| 74 | void SetNickname(Nickname nickname); | ||
| 75 | void SetInvalidName(); | ||
| 76 | |||
| 77 | Common::UUID GetCreateId() const; | ||
| 78 | FontRegion GetFontRegion() const; | ||
| 79 | FavoriteColor GetFavoriteColor() const; | ||
| 80 | Gender GetGender() const; | ||
| 81 | u8 GetHeight() const; | ||
| 82 | u8 GetBuild() const; | ||
| 83 | u8 GetType() const; | ||
| 84 | u8 GetRegionMove() const; | ||
| 85 | FacelineType GetFacelineType() const; | ||
| 86 | FacelineColor GetFacelineColor() const; | ||
| 87 | FacelineWrinkle GetFacelineWrinkle() const; | ||
| 88 | FacelineMake GetFacelineMake() const; | ||
| 89 | HairType GetHairType() const; | ||
| 90 | CommonColor GetHairColor() const; | ||
| 91 | HairFlip GetHairFlip() const; | ||
| 92 | EyeType GetEyeType() const; | ||
| 93 | CommonColor GetEyeColor() const; | ||
| 94 | u8 GetEyeScale() const; | ||
| 95 | u8 GetEyeAspect() const; | ||
| 96 | u8 GetEyeRotate() const; | ||
| 97 | u8 GetEyeX() const; | ||
| 98 | u8 GetEyeY() const; | ||
| 99 | EyebrowType GetEyebrowType() const; | ||
| 100 | CommonColor GetEyebrowColor() const; | ||
| 101 | u8 GetEyebrowScale() const; | ||
| 102 | u8 GetEyebrowAspect() const; | ||
| 103 | u8 GetEyebrowRotate() const; | ||
| 104 | u8 GetEyebrowX() const; | ||
| 105 | u8 GetEyebrowY() const; | ||
| 106 | NoseType GetNoseType() const; | ||
| 107 | u8 GetNoseScale() const; | ||
| 108 | u8 GetNoseY() const; | ||
| 109 | MouthType GetMouthType() const; | ||
| 110 | CommonColor GetMouthColor() const; | ||
| 111 | u8 GetMouthScale() const; | ||
| 112 | u8 GetMouthAspect() const; | ||
| 113 | u8 GetMouthY() const; | ||
| 114 | CommonColor GetBeardColor() const; | ||
| 115 | BeardType GetBeardType() const; | ||
| 116 | MustacheType GetMustacheType() const; | ||
| 117 | u8 GetMustacheScale() const; | ||
| 118 | u8 GetMustacheY() const; | ||
| 119 | GlassType GetGlassType() const; | ||
| 120 | CommonColor GetGlassColor() const; | ||
| 121 | u8 GetGlassScale() const; | ||
| 122 | u8 GetGlassY() const; | ||
| 123 | MoleType GetMoleType() const; | ||
| 124 | u8 GetMoleScale() const; | ||
| 125 | u8 GetMoleX() const; | ||
| 126 | u8 GetMoleY() const; | ||
| 127 | Nickname GetNickname() const; | ||
| 128 | |||
| 129 | bool operator==(const StoreData& data); | ||
| 130 | |||
| 131 | private: | ||
| 132 | CoreData core_data{}; | ||
| 133 | Common::UUID create_id{}; | ||
| 134 | u16 data_crc{}; | ||
| 135 | u16 device_crc{}; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); | ||
| 138 | |||
| 139 | struct StoreDataElement { | ||
| 140 | StoreData store_data{}; | ||
| 141 | Source source{}; | ||
| 142 | }; | ||
| 143 | static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size."); | ||
| 144 | |||
| 145 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp new file mode 100644 index 000000000..1c28e0b1b --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/mii_util.h" | ||
| 5 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 6 | #include "core/hle/service/mii/types/store_data.h" | ||
| 7 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) { | ||
| 12 | faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf; | ||
| 13 | hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f; | ||
| 14 | eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f; | ||
| 15 | eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f; | ||
| 16 | mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f; | ||
| 17 | beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f; | ||
| 18 | glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f; | ||
| 19 | glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f; | ||
| 20 | } | ||
| 21 | |||
| 22 | void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { | ||
| 23 | out_store_data.BuildBase(Gender::Male); | ||
| 24 | |||
| 25 | if (!IsValid()) { | ||
| 26 | return; | ||
| 27 | } | ||
| 28 | |||
| 29 | // TODO: We are ignoring a bunch of data from the mii_v3 | ||
| 30 | |||
| 31 | out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); | ||
| 32 | out_store_data.SetFavoriteColor( | ||
| 33 | static_cast<FavoriteColor>(mii_information.favorite_color.Value())); | ||
| 34 | out_store_data.SetHeight(height); | ||
| 35 | out_store_data.SetBuild(build); | ||
| 36 | |||
| 37 | out_store_data.SetNickname(mii_name); | ||
| 38 | out_store_data.SetFontRegion( | ||
| 39 | static_cast<FontRegion>(static_cast<u8>(region_information.font_region))); | ||
| 40 | |||
| 41 | out_store_data.SetFacelineType( | ||
| 42 | static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); | ||
| 43 | out_store_data.SetFacelineColor( | ||
| 44 | static_cast<FacelineColor>(appearance_bits1.faceline_color.Value())); | ||
| 45 | out_store_data.SetFacelineWrinkle( | ||
| 46 | static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); | ||
| 47 | out_store_data.SetFacelineMake( | ||
| 48 | static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); | ||
| 49 | |||
| 50 | out_store_data.SetHairType(static_cast<HairType>(hair_type)); | ||
| 51 | out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value())); | ||
| 52 | out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); | ||
| 53 | |||
| 54 | out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); | ||
| 55 | out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value())); | ||
| 56 | out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale)); | ||
| 57 | out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect)); | ||
| 58 | out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate)); | ||
| 59 | out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x)); | ||
| 60 | out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y)); | ||
| 61 | |||
| 62 | out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); | ||
| 63 | out_store_data.SetEyebrowColor( | ||
| 64 | static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value())); | ||
| 65 | out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale)); | ||
| 66 | out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect)); | ||
| 67 | out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate)); | ||
| 68 | out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x)); | ||
| 69 | out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y)); | ||
| 70 | |||
| 71 | out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); | ||
| 72 | out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale)); | ||
| 73 | out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y)); | ||
| 74 | |||
| 75 | out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type)); | ||
| 76 | out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value())); | ||
| 77 | out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale)); | ||
| 78 | out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect)); | ||
| 79 | out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y)); | ||
| 80 | |||
| 81 | out_store_data.SetMustacheType( | ||
| 82 | static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); | ||
| 83 | out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale)); | ||
| 84 | out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y)); | ||
| 85 | |||
| 86 | out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); | ||
| 87 | out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value())); | ||
| 88 | |||
| 89 | out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); | ||
| 90 | out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value())); | ||
| 91 | out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale)); | ||
| 92 | out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y)); | ||
| 93 | |||
| 94 | out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); | ||
| 95 | out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale)); | ||
| 96 | out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x)); | ||
| 97 | out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y)); | ||
| 98 | } | ||
| 99 | |||
| 100 | void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { | ||
| 101 | version = 1; | ||
| 102 | mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); | ||
| 103 | mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); | ||
| 104 | height = store_data.GetHeight(); | ||
| 105 | build = store_data.GetBuild(); | ||
| 106 | |||
| 107 | mii_name = store_data.GetNickname(); | ||
| 108 | region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion())); | ||
| 109 | |||
| 110 | appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType())); | ||
| 111 | appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle())); | ||
| 112 | appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake())); | ||
| 113 | |||
| 114 | hair_type = static_cast<u8>(store_data.GetHairType()); | ||
| 115 | appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip())); | ||
| 116 | |||
| 117 | appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType())); | ||
| 118 | appearance_bits4.eye_scale.Assign(store_data.GetEyeScale()); | ||
| 119 | appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect()); | ||
| 120 | appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate()); | ||
| 121 | appearance_bits4.eye_x.Assign(store_data.GetEyeX()); | ||
| 122 | appearance_bits4.eye_y.Assign(store_data.GetEyeY()); | ||
| 123 | |||
| 124 | appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType())); | ||
| 125 | appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale()); | ||
| 126 | appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect()); | ||
| 127 | appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate()); | ||
| 128 | appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX()); | ||
| 129 | appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY()); | ||
| 130 | |||
| 131 | appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType())); | ||
| 132 | appearance_bits6.nose_scale.Assign(store_data.GetNoseScale()); | ||
| 133 | appearance_bits6.nose_y.Assign(store_data.GetNoseY()); | ||
| 134 | |||
| 135 | appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType())); | ||
| 136 | appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale()); | ||
| 137 | appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect()); | ||
| 138 | appearance_bits8.mouth_y.Assign(store_data.GetMouthY()); | ||
| 139 | |||
| 140 | appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType())); | ||
| 141 | appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale()); | ||
| 142 | appearance_bits9.mustache_y.Assign(store_data.GetMustacheY()); | ||
| 143 | |||
| 144 | appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType())); | ||
| 145 | |||
| 146 | appearance_bits10.glass_scale.Assign(store_data.GetGlassScale()); | ||
| 147 | appearance_bits10.glass_y.Assign(store_data.GetGlassY()); | ||
| 148 | |||
| 149 | appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType())); | ||
| 150 | appearance_bits11.mole_scale.Assign(store_data.GetMoleScale()); | ||
| 151 | appearance_bits11.mole_x.Assign(store_data.GetMoleX()); | ||
| 152 | appearance_bits11.mole_y.Assign(store_data.GetMoleY()); | ||
| 153 | |||
| 154 | // These types are converted to V3 from a table | ||
| 155 | appearance_bits1.faceline_color.Assign( | ||
| 156 | RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor()))); | ||
| 157 | appearance_bits3.hair_color.Assign( | ||
| 158 | RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor()))); | ||
| 159 | appearance_bits4.eye_color.Assign( | ||
| 160 | RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor()))); | ||
| 161 | appearance_bits5.eyebrow_color.Assign( | ||
| 162 | RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor()))); | ||
| 163 | appearance_bits7.mouth_color.Assign( | ||
| 164 | RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor()))); | ||
| 165 | appearance_bits9.beard_color.Assign( | ||
| 166 | RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor()))); | ||
| 167 | appearance_bits10.glass_color.Assign( | ||
| 168 | RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor()))); | ||
| 169 | appearance_bits10.glass_type.Assign( | ||
| 170 | RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType()))); | ||
| 171 | |||
| 172 | crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16)); | ||
| 173 | } | ||
| 174 | |||
| 175 | u32 Ver3StoreData::IsValid() const { | ||
| 176 | bool is_valid = version == 0 || version == 3; | ||
| 177 | |||
| 178 | is_valid = is_valid && (mii_name.data[0] != '\0'); | ||
| 179 | |||
| 180 | is_valid = is_valid && (mii_information.birth_month < 13); | ||
| 181 | is_valid = is_valid && (mii_information.birth_day < 32); | ||
| 182 | is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max)); | ||
| 183 | is_valid = is_valid && (height <= MaxHeight); | ||
| 184 | is_valid = is_valid && (build <= MaxBuild); | ||
| 185 | |||
| 186 | is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max)); | ||
| 187 | is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2); | ||
| 188 | is_valid = | ||
| 189 | is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max)); | ||
| 190 | is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max)); | ||
| 191 | |||
| 192 | is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max)); | ||
| 193 | is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor); | ||
| 194 | |||
| 195 | is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max)); | ||
| 196 | is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2); | ||
| 197 | is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale); | ||
| 198 | is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect); | ||
| 199 | is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate); | ||
| 200 | is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX); | ||
| 201 | is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY); | ||
| 202 | |||
| 203 | is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max)); | ||
| 204 | is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor); | ||
| 205 | is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale); | ||
| 206 | is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect); | ||
| 207 | is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate); | ||
| 208 | is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX); | ||
| 209 | is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY); | ||
| 210 | |||
| 211 | is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max)); | ||
| 212 | is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale); | ||
| 213 | is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY); | ||
| 214 | |||
| 215 | is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max)); | ||
| 216 | is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3); | ||
| 217 | is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale); | ||
| 218 | is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect); | ||
| 219 | is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY); | ||
| 220 | |||
| 221 | is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); | ||
| 222 | is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); | ||
| 223 | is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY); | ||
| 224 | |||
| 225 | is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); | ||
| 226 | is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); | ||
| 227 | |||
| 228 | is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); | ||
| 229 | is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); | ||
| 230 | is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); | ||
| 231 | is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale); | ||
| 232 | |||
| 233 | is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); | ||
| 234 | is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); | ||
| 235 | is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX); | ||
| 236 | is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY); | ||
| 237 | |||
| 238 | return is_valid; | ||
| 239 | } | ||
| 240 | |||
| 241 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h new file mode 100644 index 000000000..47907bf7d --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.h | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | class StoreData; | ||
| 10 | |||
| 11 | // This is nn::mii::Ver3StoreData | ||
| 12 | // Based on citra HLE::Applets::MiiData and PretendoNetwork. | ||
| 13 | // https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 | ||
| 14 | // https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 | ||
| 15 | |||
| 16 | struct NfpStoreDataExtension { | ||
| 17 | void SetFromStoreData(const StoreData& store_data); | ||
| 18 | |||
| 19 | u8 faceline_color; | ||
| 20 | u8 hair_color; | ||
| 21 | u8 eye_color; | ||
| 22 | u8 eyebrow_color; | ||
| 23 | u8 mouth_color; | ||
| 24 | u8 beard_color; | ||
| 25 | u8 glass_color; | ||
| 26 | u8 glass_type; | ||
| 27 | }; | ||
| 28 | static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); | ||
| 29 | |||
| 30 | #pragma pack(push, 4) | ||
| 31 | class Ver3StoreData { | ||
| 32 | public: | ||
| 33 | void BuildToStoreData(StoreData& out_store_data) const; | ||
| 34 | void BuildFromStoreData(const StoreData& store_data); | ||
| 35 | |||
| 36 | u32 IsValid() const; | ||
| 37 | |||
| 38 | u8 version; | ||
| 39 | union { | ||
| 40 | u8 raw; | ||
| 41 | |||
| 42 | BitField<0, 1, u8> allow_copying; | ||
| 43 | BitField<1, 1, u8> profanity_flag; | ||
| 44 | BitField<2, 2, u8> region_lock; | ||
| 45 | BitField<4, 2, u8> font_region; | ||
| 46 | } region_information; | ||
| 47 | u16_be mii_id; | ||
| 48 | u64_be system_id; | ||
| 49 | u32_be specialness_and_creation_date; | ||
| 50 | std::array<u8, 6> creator_mac; | ||
| 51 | u16_be padding; | ||
| 52 | union { | ||
| 53 | u16 raw; | ||
| 54 | |||
| 55 | BitField<0, 1, u16> gender; | ||
| 56 | BitField<1, 4, u16> birth_month; | ||
| 57 | BitField<5, 5, u16> birth_day; | ||
| 58 | BitField<10, 4, u16> favorite_color; | ||
| 59 | BitField<14, 1, u16> favorite; | ||
| 60 | } mii_information; | ||
| 61 | Nickname mii_name; | ||
| 62 | u8 height; | ||
| 63 | u8 build; | ||
| 64 | union { | ||
| 65 | u8 raw; | ||
| 66 | |||
| 67 | BitField<0, 1, u8> disable_sharing; | ||
| 68 | BitField<1, 4, u8> faceline_type; | ||
| 69 | BitField<5, 3, u8> faceline_color; | ||
| 70 | } appearance_bits1; | ||
| 71 | union { | ||
| 72 | u8 raw; | ||
| 73 | |||
| 74 | BitField<0, 4, u8> faceline_wrinkle; | ||
| 75 | BitField<4, 4, u8> faceline_make; | ||
| 76 | } appearance_bits2; | ||
| 77 | u8 hair_type; | ||
| 78 | union { | ||
| 79 | u8 raw; | ||
| 80 | |||
| 81 | BitField<0, 3, u8> hair_color; | ||
| 82 | BitField<3, 1, u8> hair_flip; | ||
| 83 | } appearance_bits3; | ||
| 84 | union { | ||
| 85 | u32 raw; | ||
| 86 | |||
| 87 | BitField<0, 6, u32> eye_type; | ||
| 88 | BitField<6, 3, u32> eye_color; | ||
| 89 | BitField<9, 4, u32> eye_scale; | ||
| 90 | BitField<13, 3, u32> eye_aspect; | ||
| 91 | BitField<16, 5, u32> eye_rotate; | ||
| 92 | BitField<21, 4, u32> eye_x; | ||
| 93 | BitField<25, 5, u32> eye_y; | ||
| 94 | } appearance_bits4; | ||
| 95 | union { | ||
| 96 | u32 raw; | ||
| 97 | |||
| 98 | BitField<0, 5, u32> eyebrow_type; | ||
| 99 | BitField<5, 3, u32> eyebrow_color; | ||
| 100 | BitField<8, 4, u32> eyebrow_scale; | ||
| 101 | BitField<12, 3, u32> eyebrow_aspect; | ||
| 102 | BitField<16, 4, u32> eyebrow_rotate; | ||
| 103 | BitField<21, 4, u32> eyebrow_x; | ||
| 104 | BitField<25, 5, u32> eyebrow_y; | ||
| 105 | } appearance_bits5; | ||
| 106 | union { | ||
| 107 | u16 raw; | ||
| 108 | |||
| 109 | BitField<0, 5, u16> nose_type; | ||
| 110 | BitField<5, 4, u16> nose_scale; | ||
| 111 | BitField<9, 5, u16> nose_y; | ||
| 112 | } appearance_bits6; | ||
| 113 | union { | ||
| 114 | u16 raw; | ||
| 115 | |||
| 116 | BitField<0, 6, u16> mouth_type; | ||
| 117 | BitField<6, 3, u16> mouth_color; | ||
| 118 | BitField<9, 4, u16> mouth_scale; | ||
| 119 | BitField<13, 3, u16> mouth_aspect; | ||
| 120 | } appearance_bits7; | ||
| 121 | union { | ||
| 122 | u8 raw; | ||
| 123 | |||
| 124 | BitField<0, 5, u8> mouth_y; | ||
| 125 | BitField<5, 3, u8> mustache_type; | ||
| 126 | } appearance_bits8; | ||
| 127 | u8 allow_copying; | ||
| 128 | union { | ||
| 129 | u16 raw; | ||
| 130 | |||
| 131 | BitField<0, 3, u16> beard_type; | ||
| 132 | BitField<3, 3, u16> beard_color; | ||
| 133 | BitField<6, 4, u16> mustache_scale; | ||
| 134 | BitField<10, 5, u16> mustache_y; | ||
| 135 | } appearance_bits9; | ||
| 136 | union { | ||
| 137 | u16 raw; | ||
| 138 | |||
| 139 | BitField<0, 4, u16> glass_type; | ||
| 140 | BitField<4, 3, u16> glass_color; | ||
| 141 | BitField<7, 4, u16> glass_scale; | ||
| 142 | BitField<11, 5, u16> glass_y; | ||
| 143 | } appearance_bits10; | ||
| 144 | union { | ||
| 145 | u16 raw; | ||
| 146 | |||
| 147 | BitField<0, 1, u16> mole_type; | ||
| 148 | BitField<1, 4, u16> mole_scale; | ||
| 149 | BitField<5, 5, u16> mole_x; | ||
| 150 | BitField<10, 5, u16> mole_y; | ||
| 151 | } appearance_bits11; | ||
| 152 | |||
| 153 | Nickname author_name; | ||
| 154 | INSERT_PADDING_BYTES(0x2); | ||
| 155 | u16_be crc; | ||
| 156 | }; | ||
| 157 | static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); | ||
| 158 | #pragma pack(pop) | ||
| 159 | |||
| 160 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 49446bc42..674d2e4b2 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -28,7 +28,6 @@ | |||
| 28 | #include "core/hle/kernel/k_event.h" | 28 | #include "core/hle/kernel/k_event.h" |
| 29 | #include "core/hle/service/ipc_helpers.h" | 29 | #include "core/hle/service/ipc_helpers.h" |
| 30 | #include "core/hle/service/mii/mii_manager.h" | 30 | #include "core/hle/service/mii/mii_manager.h" |
| 31 | #include "core/hle/service/mii/types.h" | ||
| 32 | #include "core/hle/service/nfc/common/amiibo_crypto.h" | 31 | #include "core/hle/service/nfc/common/amiibo_crypto.h" |
| 33 | #include "core/hle/service/nfc/common/device.h" | 32 | #include "core/hle/service/nfc/common/device.h" |
| 34 | #include "core/hle/service/nfc/mifare_result.h" | 33 | #include "core/hle/service/nfc/mifare_result.h" |
| @@ -681,12 +680,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const { | |||
| 681 | return ResultRegistrationIsNotInitialized; | 680 | return ResultRegistrationIsNotInitialized; |
| 682 | } | 681 | } |
| 683 | 682 | ||
| 684 | Service::Mii::MiiManager manager; | 683 | Mii::CharInfo char_info{}; |
| 684 | Mii::StoreData store_data{}; | ||
| 685 | tag_data.owner_mii.BuildToStoreData(store_data); | ||
| 686 | char_info.SetFromStoreData(store_data); | ||
| 687 | |||
| 685 | const auto& settings = tag_data.settings; | 688 | const auto& settings = tag_data.settings; |
| 686 | 689 | ||
| 687 | // TODO: Validate this data | 690 | // TODO: Validate this data |
| 688 | register_info = { | 691 | register_info = { |
| 689 | .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), | 692 | .mii_char_info = char_info, |
| 690 | .creation_date = settings.init_date.GetWriteDate(), | 693 | .creation_date = settings.init_date.GetWriteDate(), |
| 691 | .amiibo_name = GetAmiiboName(settings), | 694 | .amiibo_name = GetAmiiboName(settings), |
| 692 | .font_region = settings.settings.font_region, | 695 | .font_region = settings.settings.font_region, |
| @@ -825,8 +828,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe | |||
| 825 | return ResultWrongDeviceState; | 828 | return ResultWrongDeviceState; |
| 826 | } | 829 | } |
| 827 | 830 | ||
| 828 | Service::Mii::MiiManager manager; | 831 | Service::Mii::StoreData store_data{}; |
| 829 | const auto mii = manager.BuildDefault(0); | 832 | Service::Mii::NfpStoreDataExtension extension{}; |
| 833 | store_data.BuildBase(Mii::Gender::Male); | ||
| 834 | extension.SetFromStoreData(store_data); | ||
| 835 | |||
| 830 | auto& settings = tag_data.settings; | 836 | auto& settings = tag_data.settings; |
| 831 | 837 | ||
| 832 | if (tag_data.settings.settings.amiibo_initialized == 0) { | 838 | if (tag_data.settings.settings.amiibo_initialized == 0) { |
| @@ -835,8 +841,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe | |||
| 835 | } | 841 | } |
| 836 | 842 | ||
| 837 | SetAmiiboName(settings, register_info.amiibo_name); | 843 | SetAmiiboName(settings, register_info.amiibo_name); |
| 838 | tag_data.owner_mii = manager.BuildFromStoreData(mii); | 844 | tag_data.owner_mii.BuildFromStoreData(store_data); |
| 839 | tag_data.mii_extension = manager.SetFromStoreData(mii); | 845 | tag_data.mii_extension = extension; |
| 840 | tag_data.unknown = 0; | 846 | tag_data.unknown = 0; |
| 841 | tag_data.unknown2 = {}; | 847 | tag_data.unknown2 = {}; |
| 842 | settings.country_code_id = 0; | 848 | settings.country_code_id = 0; |
| @@ -868,17 +874,19 @@ Result NfcDevice::RestoreAmiibo() { | |||
| 868 | } | 874 | } |
| 869 | 875 | ||
| 870 | Result NfcDevice::Format() { | 876 | Result NfcDevice::Format() { |
| 871 | auto result1 = DeleteApplicationArea(); | 877 | Result result = ResultSuccess; |
| 872 | auto result2 = DeleteRegisterInfo(); | ||
| 873 | 878 | ||
| 874 | if (result1.IsError()) { | 879 | if (device_state == DeviceState::TagFound) { |
| 875 | return result1; | 880 | result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); |
| 876 | } | 881 | } |
| 877 | 882 | ||
| 878 | if (result2.IsError()) { | 883 | if (result.IsError()) { |
| 879 | return result2; | 884 | return result; |
| 880 | } | 885 | } |
| 881 | 886 | ||
| 887 | DeleteApplicationArea(); | ||
| 888 | DeleteRegisterInfo(); | ||
| 889 | |||
| 882 | return Flush(); | 890 | return Flush(); |
| 883 | } | 891 | } |
| 884 | 892 | ||
| @@ -1453,7 +1461,7 @@ void NfcDevice::UpdateRegisterInfoCrc() { | |||
| 1453 | 1461 | ||
| 1454 | void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, | 1462 | void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, |
| 1455 | const NFP::EncryptedNTAG215File& encrypted_file) const { | 1463 | const NFP::EncryptedNTAG215File& encrypted_file) const { |
| 1456 | Service::Mii::MiiManager manager; | 1464 | Service::Mii::StoreData store_data{}; |
| 1457 | auto& settings = stubbed_tag_data.settings; | 1465 | auto& settings = stubbed_tag_data.settings; |
| 1458 | 1466 | ||
| 1459 | stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); | 1467 | stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); |
| @@ -1467,7 +1475,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, | |||
| 1467 | SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); | 1475 | SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); |
| 1468 | settings.settings.font_region.Assign(0); | 1476 | settings.settings.font_region.Assign(0); |
| 1469 | settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); | 1477 | settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); |
| 1470 | stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); | 1478 | store_data.BuildBase(Mii::Gender::Male); |
| 1479 | stubbed_tag_data.owner_mii.BuildFromStoreData(store_data); | ||
| 1471 | 1480 | ||
| 1472 | // Admin info | 1481 | // Admin info |
| 1473 | settings.settings.amiibo_initialized.Assign(1); | 1482 | settings.settings.amiibo_initialized.Assign(1); |
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index aed12a7f8..f96d21220 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h | |||
| @@ -6,7 +6,9 @@ | |||
| 6 | #include <array> | 6 | #include <array> |
| 7 | 7 | ||
| 8 | #include "common/swap.h" | 8 | #include "common/swap.h" |
| 9 | #include "core/hle/service/mii/types.h" | 9 | #include "core/hle/service/mii/types/char_info.h" |
| 10 | #include "core/hle/service/mii/types/store_data.h" | ||
| 11 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 10 | #include "core/hle/service/nfc/nfc_types.h" | 12 | #include "core/hle/service/nfc/nfc_types.h" |
| 11 | 13 | ||
| 12 | namespace Service::NFP { | 14 | namespace Service::NFP { |
| @@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); | |||
| 322 | 324 | ||
| 323 | // This is nn::nfp::RegisterInfoPrivate | 325 | // This is nn::nfp::RegisterInfoPrivate |
| 324 | struct RegisterInfoPrivate { | 326 | struct RegisterInfoPrivate { |
| 325 | Service::Mii::MiiStoreData mii_store_data; | 327 | Service::Mii::StoreData mii_store_data; |
| 326 | WriteDate creation_date; | 328 | WriteDate creation_date; |
| 327 | AmiiboName amiibo_name; | 329 | AmiiboName amiibo_name; |
| 328 | u8 font_region; | 330 | u8 font_region; |
diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp new file mode 100644 index 000000000..c26019ec0 --- /dev/null +++ b/src/core/hle/service/ngc/ngc.cpp | |||
| @@ -0,0 +1,150 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/string_util.h" | ||
| 5 | #include "core/core.h" | ||
| 6 | #include "core/hle/service/ipc_helpers.h" | ||
| 7 | #include "core/hle/service/ngc/ngc.h" | ||
| 8 | #include "core/hle/service/server_manager.h" | ||
| 9 | #include "core/hle/service/service.h" | ||
| 10 | |||
| 11 | namespace Service::NGC { | ||
| 12 | |||
| 13 | class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> { | ||
| 14 | public: | ||
| 15 | explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { | ||
| 16 | // clang-format off | ||
| 17 | static const FunctionInfo functions[] = { | ||
| 18 | {0, &NgctServiceImpl::Match, "Match"}, | ||
| 19 | {1, &NgctServiceImpl::Filter, "Filter"}, | ||
| 20 | }; | ||
| 21 | // clang-format on | ||
| 22 | |||
| 23 | RegisterHandlers(functions); | ||
| 24 | } | ||
| 25 | |||
| 26 | private: | ||
| 27 | void Match(HLERequestContext& ctx) { | ||
| 28 | const auto buffer = ctx.ReadBuffer(); | ||
| 29 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 30 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 31 | |||
| 32 | LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); | ||
| 33 | |||
| 34 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 35 | rb.Push(ResultSuccess); | ||
| 36 | // Return false since we don't censor anything | ||
| 37 | rb.Push(false); | ||
| 38 | } | ||
| 39 | |||
| 40 | void Filter(HLERequestContext& ctx) { | ||
| 41 | const auto buffer = ctx.ReadBuffer(); | ||
| 42 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 43 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 44 | |||
| 45 | LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); | ||
| 46 | |||
| 47 | // Return the same string since we don't censor anything | ||
| 48 | ctx.WriteBuffer(buffer); | ||
| 49 | |||
| 50 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 51 | rb.Push(ResultSuccess); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> { | ||
| 56 | public: | ||
| 57 | explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") { | ||
| 58 | // clang-format off | ||
| 59 | static const FunctionInfo functions[] = { | ||
| 60 | {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"}, | ||
| 61 | {1, &NgcServiceImpl::Check, "Check"}, | ||
| 62 | {2, &NgcServiceImpl::Mask, "Mask"}, | ||
| 63 | {3, &NgcServiceImpl::Reload, "Reload"}, | ||
| 64 | }; | ||
| 65 | // clang-format on | ||
| 66 | |||
| 67 | RegisterHandlers(functions); | ||
| 68 | } | ||
| 69 | |||
| 70 | private: | ||
| 71 | static constexpr u32 NgcContentVersion = 1; | ||
| 72 | |||
| 73 | // This is nn::ngc::detail::ProfanityFilterOption | ||
| 74 | struct ProfanityFilterOption { | ||
| 75 | INSERT_PADDING_BYTES_NOINIT(0x20); | ||
| 76 | }; | ||
| 77 | static_assert(sizeof(ProfanityFilterOption) == 0x20, | ||
| 78 | "ProfanityFilterOption has incorrect size"); | ||
| 79 | |||
| 80 | void GetContentVersion(HLERequestContext& ctx) { | ||
| 81 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 82 | |||
| 83 | // This calls nn::ngc::ProfanityFilter::GetContentVersion | ||
| 84 | const u32 version = NgcContentVersion; | ||
| 85 | |||
| 86 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 87 | rb.Push(ResultSuccess); | ||
| 88 | rb.Push(version); | ||
| 89 | } | ||
| 90 | |||
| 91 | void Check(HLERequestContext& ctx) { | ||
| 92 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 93 | |||
| 94 | struct InputParameters { | ||
| 95 | u32 flags; | ||
| 96 | ProfanityFilterOption option; | ||
| 97 | }; | ||
| 98 | |||
| 99 | IPC::RequestParser rp{ctx}; | ||
| 100 | [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); | ||
| 101 | [[maybe_unused]] const auto input = ctx.ReadBuffer(0); | ||
| 102 | |||
| 103 | // This calls nn::ngc::ProfanityFilter::CheckProfanityWords | ||
| 104 | const u32 out_flags = 0; | ||
| 105 | |||
| 106 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 107 | rb.Push(ResultSuccess); | ||
| 108 | rb.Push(out_flags); | ||
| 109 | } | ||
| 110 | |||
| 111 | void Mask(HLERequestContext& ctx) { | ||
| 112 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 113 | |||
| 114 | struct InputParameters { | ||
| 115 | u32 flags; | ||
| 116 | ProfanityFilterOption option; | ||
| 117 | }; | ||
| 118 | |||
| 119 | IPC::RequestParser rp{ctx}; | ||
| 120 | [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); | ||
| 121 | const auto input = ctx.ReadBuffer(0); | ||
| 122 | |||
| 123 | // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText | ||
| 124 | const u32 out_flags = 0; | ||
| 125 | ctx.WriteBuffer(input); | ||
| 126 | |||
| 127 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 128 | rb.Push(ResultSuccess); | ||
| 129 | rb.Push(out_flags); | ||
| 130 | } | ||
| 131 | |||
| 132 | void Reload(HLERequestContext& ctx) { | ||
| 133 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 134 | |||
| 135 | // This reloads the database. | ||
| 136 | |||
| 137 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 138 | rb.Push(ResultSuccess); | ||
| 139 | } | ||
| 140 | }; | ||
| 141 | |||
| 142 | void LoopProcess(Core::System& system) { | ||
| 143 | auto server_manager = std::make_unique<ServerManager>(system); | ||
| 144 | |||
| 145 | server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system)); | ||
| 146 | server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system)); | ||
| 147 | ServerManager::RunServer(std::move(server_manager)); | ||
| 148 | } | ||
| 149 | |||
| 150 | } // namespace Service::NGC | ||
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h index 27c34dad4..823b1aa81 100644 --- a/src/core/hle/service/ngct/ngct.h +++ b/src/core/hle/service/ngc/ngc.h | |||
| @@ -7,8 +7,8 @@ namespace Core { | |||
| 7 | class System; | 7 | class System; |
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | namespace Service::NGCT { | 10 | namespace Service::NGC { |
| 11 | 11 | ||
| 12 | void LoopProcess(Core::System& system); | 12 | void LoopProcess(Core::System& system); |
| 13 | 13 | ||
| 14 | } // namespace Service::NGCT | 14 | } // namespace Service::NGC |
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp deleted file mode 100644 index 493c80ed2..000000000 --- a/src/core/hle/service/ngct/ngct.cpp +++ /dev/null | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/string_util.h" | ||
| 5 | #include "core/core.h" | ||
| 6 | #include "core/hle/service/ipc_helpers.h" | ||
| 7 | #include "core/hle/service/ngct/ngct.h" | ||
| 8 | #include "core/hle/service/server_manager.h" | ||
| 9 | #include "core/hle/service/service.h" | ||
| 10 | |||
| 11 | namespace Service::NGCT { | ||
| 12 | |||
| 13 | class IService final : public ServiceFramework<IService> { | ||
| 14 | public: | ||
| 15 | explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { | ||
| 16 | // clang-format off | ||
| 17 | static const FunctionInfo functions[] = { | ||
| 18 | {0, &IService::Match, "Match"}, | ||
| 19 | {1, &IService::Filter, "Filter"}, | ||
| 20 | }; | ||
| 21 | // clang-format on | ||
| 22 | |||
| 23 | RegisterHandlers(functions); | ||
| 24 | } | ||
| 25 | |||
| 26 | private: | ||
| 27 | void Match(HLERequestContext& ctx) { | ||
| 28 | const auto buffer = ctx.ReadBuffer(); | ||
| 29 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 30 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 31 | |||
| 32 | LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); | ||
| 33 | |||
| 34 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 35 | rb.Push(ResultSuccess); | ||
| 36 | // Return false since we don't censor anything | ||
| 37 | rb.Push(false); | ||
| 38 | } | ||
| 39 | |||
| 40 | void Filter(HLERequestContext& ctx) { | ||
| 41 | const auto buffer = ctx.ReadBuffer(); | ||
| 42 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 43 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 44 | |||
| 45 | LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); | ||
| 46 | |||
| 47 | // Return the same string since we don't censor anything | ||
| 48 | ctx.WriteBuffer(buffer); | ||
| 49 | |||
| 50 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 51 | rb.Push(ResultSuccess); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | void LoopProcess(Core::System& system) { | ||
| 56 | auto server_manager = std::make_unique<ServerManager>(system); | ||
| 57 | |||
| 58 | server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system)); | ||
| 59 | ServerManager::RunServer(std::move(server_manager)); | ||
| 60 | } | ||
| 61 | |||
| 62 | } // namespace Service::NGCT | ||
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp index a51ca5444..0ca05257e 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.cpp +++ b/src/core/hle/service/nvdrv/core/nvmap.cpp | |||
| @@ -160,8 +160,8 @@ u32 NvMap::PinHandle(NvMap::Handle::Id handle) { | |||
| 160 | u32 address{}; | 160 | u32 address{}; |
| 161 | auto& smmu_allocator = host1x.Allocator(); | 161 | auto& smmu_allocator = host1x.Allocator(); |
| 162 | auto& smmu_memory_manager = host1x.MemoryManager(); | 162 | auto& smmu_memory_manager = host1x.MemoryManager(); |
| 163 | while (!(address = | 163 | while ((address = smmu_allocator.Allocate( |
| 164 | smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) { | 164 | static_cast<u32>(handle_description->aligned_size))) == 0) { |
| 165 | // Free handles until the allocation succeeds | 165 | // Free handles until the allocation succeeds |
| 166 | std::scoped_lock queueLock(unmap_queue_lock); | 166 | std::scoped_lock queueLock(unmap_queue_lock); |
| 167 | if (auto freeHandleDesc{unmap_queue.front()}) { | 167 | if (auto freeHandleDesc{unmap_queue.front()}) { |
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 07e570a9f..7d7bb8687 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp | |||
| @@ -204,9 +204,11 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) { | |||
| 204 | if (!mapping->fixed) { | 204 | if (!mapping->fixed) { |
| 205 | auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; | 205 | auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; |
| 206 | u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; | 206 | u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; |
| 207 | u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; | ||
| 208 | u64 aligned_size{Common::AlignUp(mapping->size, page_size)}; | ||
| 207 | 209 | ||
| 208 | allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits), | 210 | allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits), |
| 209 | static_cast<u32>(mapping->size >> page_size_bits)); | 211 | static_cast<u32>(aligned_size >> page_size_bits)); |
| 210 | } | 212 | } |
| 211 | 213 | ||
| 212 | // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state | 214 | // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state |
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index b16f9933f..dc6917d5d 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp | |||
| @@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, | |||
| 449 | case NativeWindowScalingMode::ScaleToWindow: | 449 | case NativeWindowScalingMode::ScaleToWindow: |
| 450 | case NativeWindowScalingMode::ScaleCrop: | 450 | case NativeWindowScalingMode::ScaleCrop: |
| 451 | case NativeWindowScalingMode::NoScaleCrop: | 451 | case NativeWindowScalingMode::NoScaleCrop: |
| 452 | case NativeWindowScalingMode::PreserveAspectRatio: | ||
| 452 | break; | 453 | break; |
| 453 | default: | 454 | default: |
| 454 | LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); | 455 | LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); |
diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h index 61cca5b01..36d6cde3d 100644 --- a/src/core/hle/service/nvnflinger/window.h +++ b/src/core/hle/service/nvnflinger/window.h | |||
| @@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 { | |||
| 41 | ScaleToWindow = 1, | 41 | ScaleToWindow = 1, |
| 42 | ScaleCrop = 2, | 42 | ScaleCrop = 2, |
| 43 | NoScaleCrop = 3, | 43 | NoScaleCrop = 3, |
| 44 | PreserveAspectRatio = 4, | ||
| 44 | }; | 45 | }; |
| 45 | 46 | ||
| 46 | /// Transform parameter for QueueBuffer | 47 | /// Transform parameter for QueueBuffer |
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 69cdb5918..0ad607391 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp | |||
| @@ -43,7 +43,7 @@ | |||
| 43 | #include "core/hle/service/ncm/ncm.h" | 43 | #include "core/hle/service/ncm/ncm.h" |
| 44 | #include "core/hle/service/nfc/nfc.h" | 44 | #include "core/hle/service/nfc/nfc.h" |
| 45 | #include "core/hle/service/nfp/nfp.h" | 45 | #include "core/hle/service/nfp/nfp.h" |
| 46 | #include "core/hle/service/ngct/ngct.h" | 46 | #include "core/hle/service/ngc/ngc.h" |
| 47 | #include "core/hle/service/nifm/nifm.h" | 47 | #include "core/hle/service/nifm/nifm.h" |
| 48 | #include "core/hle/service/nim/nim.h" | 48 | #include "core/hle/service/nim/nim.h" |
| 49 | #include "core/hle/service/npns/npns.h" | 49 | #include "core/hle/service/npns/npns.h" |
| @@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system | |||
| 257 | kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); | 257 | kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); |
| 258 | kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); | 258 | kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); |
| 259 | kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); | 259 | kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); |
| 260 | kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); | 260 | kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); }); |
| 261 | kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); | 261 | kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); |
| 262 | kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); | 262 | kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); |
| 263 | kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); | 263 | kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); |
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 45b2c43b7..d539ed0f4 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h | |||
| @@ -79,8 +79,8 @@ protected: | |||
| 79 | using HandlerFnP = void (Self::*)(HLERequestContext&); | 79 | using HandlerFnP = void (Self::*)(HLERequestContext&); |
| 80 | 80 | ||
| 81 | /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. | 81 | /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. |
| 82 | [[nodiscard]] std::scoped_lock<std::mutex> LockService() { | 82 | [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() { |
| 83 | return std::scoped_lock{lock_service}; | 83 | return std::unique_lock{lock_service}; |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | /// System context that the service operates under. | 86 | /// System context that the service operates under. |
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 11f8efbac..85849d5f3 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp | |||
| @@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) { | |||
| 170 | } | 170 | } |
| 171 | 171 | ||
| 172 | void BSD::Select(HLERequestContext& ctx) { | 172 | void BSD::Select(HLERequestContext& ctx) { |
| 173 | LOG_WARNING(Service, "(STUBBED) called"); | 173 | LOG_DEBUG(Service, "(STUBBED) called"); |
| 174 | 174 | ||
| 175 | IPC::ResponseBuilder rb{ctx, 4}; | 175 | IPC::ResponseBuilder rb{ctx, 4}; |
| 176 | 176 | ||
| @@ -1029,6 +1029,11 @@ BSD::~BSD() { | |||
| 1029 | } | 1029 | } |
| 1030 | } | 1030 | } |
| 1031 | 1031 | ||
| 1032 | std::unique_lock<std::mutex> BSD::LockService() { | ||
| 1033 | // Do not lock socket IClient instances. | ||
| 1034 | return {}; | ||
| 1035 | } | ||
| 1036 | |||
| 1032 | BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { | 1037 | BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { |
| 1033 | // clang-format off | 1038 | // clang-format off |
| 1034 | static const FunctionInfo functions[] = { | 1039 | static const FunctionInfo functions[] = { |
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 430edb97c..161f22b9b 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h | |||
| @@ -186,6 +186,9 @@ private: | |||
| 186 | 186 | ||
| 187 | // Callback identifier for the OnProxyPacketReceived event. | 187 | // Callback identifier for the OnProxyPacketReceived event. |
| 188 | Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; | 188 | Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; |
| 189 | |||
| 190 | protected: | ||
| 191 | virtual std::unique_lock<std::mutex> LockService() override; | ||
| 189 | }; | 192 | }; |
| 190 | 193 | ||
| 191 | class BSDCFG final : public ServiceFramework<BSDCFG> { | 194 | class BSDCFG final : public ServiceFramework<BSDCFG> { |
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index bac21752a..491b76d48 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp | |||
| @@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 { | |||
| 19 | Dp, | 19 | Dp, |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| 22 | // This is nn::nsd::EnvironmentIdentifier | ||
| 23 | struct EnvironmentIdentifier { | ||
| 24 | std::array<u8, 8> identifier; | ||
| 25 | }; | ||
| 26 | static_assert(sizeof(EnvironmentIdentifier) == 0x8); | ||
| 27 | |||
| 22 | NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { | 28 | NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { |
| 23 | // clang-format off | 29 | // clang-format off |
| 24 | static const FunctionInfo functions[] = { | 30 | static const FunctionInfo functions[] = { |
| @@ -101,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) { | |||
| 101 | } | 107 | } |
| 102 | 108 | ||
| 103 | void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { | 109 | void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { |
| 104 | const std::string environment_identifier = "lp1"; | 110 | constexpr EnvironmentIdentifier lp1 = { |
| 105 | ctx.WriteBuffer(environment_identifier); | 111 | .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}}; |
| 112 | ctx.WriteBuffer(lp1); | ||
| 106 | 113 | ||
| 107 | IPC::ResponseBuilder rb{ctx, 2}; | 114 | IPC::ResponseBuilder rb{ctx, 2}; |
| 108 | rb.Push(ResultSuccess); | 115 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 22e4a6f49..c657c4efd 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp | |||
| @@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte | |||
| 150 | const std::string host = Common::StringFromBuffer(host_buffer); | 150 | const std::string host = Common::StringFromBuffer(host_buffer); |
| 151 | // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. | 151 | // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. |
| 152 | 152 | ||
| 153 | // Prevent resolution of Nintendo servers | ||
| 154 | if (host.find("srv.nintendo.net") != std::string::npos) { | ||
| 155 | LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); | ||
| 156 | return {0, GetAddrInfoError::AGAIN}; | ||
| 157 | } | ||
| 158 | |||
| 153 | auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); | 159 | auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); |
| 154 | if (!res.has_value()) { | 160 | if (!res.has_value()) { |
| 155 | return {0, Translate(res.error())}; | 161 | return {0, Translate(res.error())}; |
| @@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext | |||
| 261 | const auto host_buffer = ctx.ReadBuffer(0); | 267 | const auto host_buffer = ctx.ReadBuffer(0); |
| 262 | const std::string host = Common::StringFromBuffer(host_buffer); | 268 | const std::string host = Common::StringFromBuffer(host_buffer); |
| 263 | 269 | ||
| 270 | // Prevent resolution of Nintendo servers | ||
| 271 | if (host.find("srv.nintendo.net") != std::string::npos) { | ||
| 272 | LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); | ||
| 273 | return {0, GetAddrInfoError::AGAIN}; | ||
| 274 | } | ||
| 275 | |||
| 264 | std::optional<std::string> service = std::nullopt; | 276 | std::optional<std::string> service = std::nullopt; |
| 265 | if (ctx.CanReadBuffer(1)) { | 277 | if (ctx.CanReadBuffer(1)) { |
| 266 | const std::span<const u8> service_buffer = ctx.ReadBuffer(1); | 278 | const std::span<const u8> service_buffer = ctx.ReadBuffer(1); |
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 77426c46e..f86af01a4 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h | |||
| @@ -18,7 +18,9 @@ enum class Errno : u32 { | |||
| 18 | AGAIN = 11, | 18 | AGAIN = 11, |
| 19 | INVAL = 22, | 19 | INVAL = 22, |
| 20 | MFILE = 24, | 20 | MFILE = 24, |
| 21 | PIPE = 32, | ||
| 21 | MSGSIZE = 90, | 22 | MSGSIZE = 90, |
| 23 | CONNABORTED = 103, | ||
| 22 | CONNRESET = 104, | 24 | CONNRESET = 104, |
| 23 | NOTCONN = 107, | 25 | NOTCONN = 107, |
| 24 | TIMEDOUT = 110, | 26 | TIMEDOUT = 110, |
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index c1187209f..aed05250c 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp | |||
| @@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) { | |||
| 23 | return Errno::INVAL; | 23 | return Errno::INVAL; |
| 24 | case Network::Errno::MFILE: | 24 | case Network::Errno::MFILE: |
| 25 | return Errno::MFILE; | 25 | return Errno::MFILE; |
| 26 | case Network::Errno::PIPE: | ||
| 27 | return Errno::PIPE; | ||
| 26 | case Network::Errno::NOTCONN: | 28 | case Network::Errno::NOTCONN: |
| 27 | return Errno::NOTCONN; | 29 | return Errno::NOTCONN; |
| 28 | case Network::Errno::TIMEDOUT: | 30 | case Network::Errno::TIMEDOUT: |
| 29 | return Errno::TIMEDOUT; | 31 | return Errno::TIMEDOUT; |
| 32 | case Network::Errno::CONNABORTED: | ||
| 33 | return Errno::CONNABORTED; | ||
| 30 | case Network::Errno::CONNRESET: | 34 | case Network::Errno::CONNRESET: |
| 31 | return Errno::CONNRESET; | 35 | return Errno::CONNRESET; |
| 32 | case Network::Errno::INPROGRESS: | 36 | case Network::Errno::INPROGRESS: |
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 2cba9e5c9..6c8427b0d 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp | |||
| @@ -139,7 +139,6 @@ private: | |||
| 139 | bool do_not_close_socket = false; | 139 | bool do_not_close_socket = false; |
| 140 | bool get_server_cert_chain = false; | 140 | bool get_server_cert_chain = false; |
| 141 | std::shared_ptr<Network::SocketBase> socket; | 141 | std::shared_ptr<Network::SocketBase> socket; |
| 142 | bool did_set_host_name = false; | ||
| 143 | bool did_handshake = false; | 142 | bool did_handshake = false; |
| 144 | 143 | ||
| 145 | Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { | 144 | Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { |
| @@ -174,11 +173,7 @@ private: | |||
| 174 | Result SetHostNameImpl(const std::string& hostname) { | 173 | Result SetHostNameImpl(const std::string& hostname) { |
| 175 | LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); | 174 | LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); |
| 176 | ASSERT(!did_handshake); | 175 | ASSERT(!did_handshake); |
| 177 | Result res = backend->SetHostName(hostname); | 176 | return backend->SetHostName(hostname); |
| 178 | if (res == ResultSuccess) { | ||
| 179 | did_set_host_name = true; | ||
| 180 | } | ||
| 181 | return res; | ||
| 182 | } | 177 | } |
| 183 | 178 | ||
| 184 | Result SetVerifyOptionImpl(u32 option) { | 179 | Result SetVerifyOptionImpl(u32 option) { |
| @@ -208,9 +203,6 @@ private: | |||
| 208 | 203 | ||
| 209 | Result DoHandshakeImpl() { | 204 | Result DoHandshakeImpl() { |
| 210 | ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); | 205 | ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); |
| 211 | ASSERT_OR_EXECUTE_MSG( | ||
| 212 | did_set_host_name, { return ResultInternalError; }, | ||
| 213 | "Expected SetHostName before DoHandshake"); | ||
| 214 | Result res = backend->DoHandshake(); | 206 | Result res = backend->DoHandshake(); |
| 215 | did_handshake = res.IsSuccess(); | 207 | did_handshake = res.IsSuccess(); |
| 216 | return res; | 208 | return res; |
diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index b2dd37cd4..5714e6f3c 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp | |||
| @@ -167,9 +167,8 @@ public: | |||
| 167 | } | 167 | } |
| 168 | 168 | ||
| 169 | ~SSLConnectionBackendOpenSSL() { | 169 | ~SSLConnectionBackendOpenSSL() { |
| 170 | // these are null-tolerant: | 170 | // this is null-tolerant: |
| 171 | SSL_free(ssl); | 171 | SSL_free(ssl); |
| 172 | BIO_free(bio); | ||
| 173 | } | 172 | } |
| 174 | 173 | ||
| 175 | static void KeyLogCallback(const SSL* ssl, const char* line) { | 174 | static void KeyLogCallback(const SSL* ssl, const char* line) { |
diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index bda12b761..212057cfc 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp | |||
| @@ -31,9 +31,9 @@ CredHandle cred_handle; | |||
| 31 | static void OneTimeInit() { | 31 | static void OneTimeInit() { |
| 32 | schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; | 32 | schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; |
| 33 | schannel_cred.dwFlags = | 33 | schannel_cred.dwFlags = |
| 34 | SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols | 34 | SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols |
| 35 | SCH_CRED_AUTO_CRED_VALIDATION | // validate certs | 35 | SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names |
| 36 | SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate | 36 | SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate |
| 37 | // ^ I'm assuming that nobody would want to connect Yuzu to a | 37 | // ^ I'm assuming that nobody would want to connect Yuzu to a |
| 38 | // service that requires some OS-provided corporate client | 38 | // service that requires some OS-provided corporate client |
| 39 | // certificate, and presenting one to some arbitrary server | 39 | // certificate, and presenting one to some arbitrary server |
| @@ -227,16 +227,15 @@ public: | |||
| 227 | ciphertext_read_buf.size()); | 227 | ciphertext_read_buf.size()); |
| 228 | } | 228 | } |
| 229 | 229 | ||
| 230 | const SECURITY_STATUS ret = | 230 | char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr; |
| 231 | InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr, | 231 | const SECURITY_STATUS ret = InitializeSecurityContextA( |
| 232 | // Caller ensured we have set a hostname: | 232 | &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req, |
| 233 | const_cast<char*>(hostname.value().c_str()), req, | 233 | 0, // Reserved1 |
| 234 | 0, // Reserved1 | 234 | 0, // TargetDataRep not used with Schannel |
| 235 | 0, // TargetDataRep not used with Schannel | 235 | initial_call_done ? &input_desc : nullptr, |
| 236 | initial_call_done ? &input_desc : nullptr, | 236 | 0, // Reserved2 |
| 237 | 0, // Reserved2 | 237 | initial_call_done ? nullptr : &ctxt, &output_desc, &attr, |
| 238 | initial_call_done ? nullptr : &ctxt, &output_desc, &attr, | 238 | nullptr); // ptsExpiry |
| 239 | nullptr); // ptsExpiry | ||
| 240 | 239 | ||
| 241 | if (output_buffers[0].pvBuffer) { | 240 | if (output_buffers[0].pvBuffer) { |
| 242 | const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer), | 241 | const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer), |
| @@ -478,7 +477,8 @@ public: | |||
| 478 | return ResultInternalError; | 477 | return ResultInternalError; |
| 479 | } | 478 | } |
| 480 | PCCERT_CONTEXT some_cert = nullptr; | 479 | PCCERT_CONTEXT some_cert = nullptr; |
| 481 | while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { | 480 | while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) != |
| 481 | nullptr) { | ||
| 482 | out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), | 482 | out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), |
| 483 | static_cast<u8*>(some_cert->pbCertEncoded) + | 483 | static_cast<u8*>(some_cert->pbCertEncoded) + |
| 484 | some_cert->cbCertEncoded); | 484 | some_cert->cbCertEncoded); |
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp index 370678f48..c48914f64 100644 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp | |||
| @@ -100,7 +100,7 @@ public: | |||
| 100 | 100 | ||
| 101 | Result DoHandshake() override { | 101 | Result DoHandshake() override { |
| 102 | OSStatus status = SSLHandshake(context); | 102 | OSStatus status = SSLHandshake(context); |
| 103 | return HandleReturn("SSLHandshake", 0, status).Code(); | 103 | return HandleReturn("SSLHandshake", 0, status); |
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | Result Read(size_t* out_size, std::span<u8> data) override { | 106 | Result Read(size_t* out_size, std::span<u8> data) override { |
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 6bb02393c..2eb978379 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp | |||
| @@ -217,7 +217,7 @@ private: | |||
| 217 | IPC::ResponseBuilder rb{ctx, 6}; | 217 | IPC::ResponseBuilder rb{ctx, 6}; |
| 218 | rb.Push(ResultSuccess); | 218 | rb.Push(ResultSuccess); |
| 219 | 219 | ||
| 220 | if (Settings::values.use_docked_mode.GetValue()) { | 220 | if (Settings::IsDockedMode()) { |
| 221 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); | 221 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); |
| 222 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); | 222 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); |
| 223 | } else { | 223 | } else { |
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 28f89c599..a983f23ea 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp | |||
| @@ -39,19 +39,41 @@ namespace Network { | |||
| 39 | 39 | ||
| 40 | namespace { | 40 | namespace { |
| 41 | 41 | ||
| 42 | enum class CallType { | ||
| 43 | Send, | ||
| 44 | Other, | ||
| 45 | }; | ||
| 46 | |||
| 42 | #ifdef _WIN32 | 47 | #ifdef _WIN32 |
| 43 | 48 | ||
| 44 | using socklen_t = int; | 49 | using socklen_t = int; |
| 45 | 50 | ||
| 51 | SOCKET interrupt_socket = static_cast<SOCKET>(-1); | ||
| 52 | |||
| 53 | void InterruptSocketOperations() { | ||
| 54 | closesocket(interrupt_socket); | ||
| 55 | } | ||
| 56 | |||
| 57 | void AcknowledgeInterrupt() { | ||
| 58 | interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | ||
| 59 | } | ||
| 60 | |||
| 46 | void Initialize() { | 61 | void Initialize() { |
| 47 | WSADATA wsa_data; | 62 | WSADATA wsa_data; |
| 48 | (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); | 63 | (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); |
| 64 | |||
| 65 | AcknowledgeInterrupt(); | ||
| 49 | } | 66 | } |
| 50 | 67 | ||
| 51 | void Finalize() { | 68 | void Finalize() { |
| 69 | InterruptSocketOperations(); | ||
| 52 | WSACleanup(); | 70 | WSACleanup(); |
| 53 | } | 71 | } |
| 54 | 72 | ||
| 73 | SOCKET GetInterruptSocket() { | ||
| 74 | return interrupt_socket; | ||
| 75 | } | ||
| 76 | |||
| 55 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { | 77 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { |
| 56 | sockaddr_in result; | 78 | sockaddr_in result; |
| 57 | 79 | ||
| @@ -96,7 +118,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { | |||
| 96 | return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; | 118 | return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; |
| 97 | } | 119 | } |
| 98 | 120 | ||
| 99 | Errno TranslateNativeError(int e) { | 121 | Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { |
| 100 | switch (e) { | 122 | switch (e) { |
| 101 | case 0: | 123 | case 0: |
| 102 | return Errno::SUCCESS; | 124 | return Errno::SUCCESS; |
| @@ -112,6 +134,14 @@ Errno TranslateNativeError(int e) { | |||
| 112 | return Errno::AGAIN; | 134 | return Errno::AGAIN; |
| 113 | case WSAECONNREFUSED: | 135 | case WSAECONNREFUSED: |
| 114 | return Errno::CONNREFUSED; | 136 | return Errno::CONNREFUSED; |
| 137 | case WSAECONNABORTED: | ||
| 138 | if (call_type == CallType::Send) { | ||
| 139 | // Winsock yields WSAECONNABORTED from `send` in situations where Unix | ||
| 140 | // systems, and actual Switches, yield EPIPE. | ||
| 141 | return Errno::PIPE; | ||
| 142 | } else { | ||
| 143 | return Errno::CONNABORTED; | ||
| 144 | } | ||
| 115 | case WSAECONNRESET: | 145 | case WSAECONNRESET: |
| 116 | return Errno::CONNRESET; | 146 | return Errno::CONNRESET; |
| 117 | case WSAEHOSTUNREACH: | 147 | case WSAEHOSTUNREACH: |
| @@ -144,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD; | |||
| 144 | constexpr int SD_SEND = SHUT_WR; | 174 | constexpr int SD_SEND = SHUT_WR; |
| 145 | constexpr int SD_BOTH = SHUT_RDWR; | 175 | constexpr int SD_BOTH = SHUT_RDWR; |
| 146 | 176 | ||
| 147 | void Initialize() {} | 177 | int interrupt_pipe_fd[2] = {-1, -1}; |
| 148 | 178 | ||
| 149 | void Finalize() {} | 179 | void Initialize() { |
| 180 | if (pipe(interrupt_pipe_fd) != 0) { | ||
| 181 | LOG_ERROR(Network, "Failed to create interrupt pipe!"); | ||
| 182 | } | ||
| 183 | int flags = fcntl(interrupt_pipe_fd[0], F_GETFL); | ||
| 184 | ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0, | ||
| 185 | "Failed to set nonblocking state for interrupt pipe"); | ||
| 186 | } | ||
| 187 | |||
| 188 | void Finalize() { | ||
| 189 | if (interrupt_pipe_fd[0] >= 0) { | ||
| 190 | close(interrupt_pipe_fd[0]); | ||
| 191 | } | ||
| 192 | if (interrupt_pipe_fd[1] >= 0) { | ||
| 193 | close(interrupt_pipe_fd[1]); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | void InterruptSocketOperations() { | ||
| 198 | u8 value = 0; | ||
| 199 | ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1); | ||
| 200 | } | ||
| 201 | |||
| 202 | void AcknowledgeInterrupt() { | ||
| 203 | u8 value = 0; | ||
| 204 | ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value)); | ||
| 205 | if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) { | ||
| 206 | LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown"); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | SOCKET GetInterruptSocket() { | ||
| 211 | return interrupt_pipe_fd[0]; | ||
| 212 | } | ||
| 150 | 213 | ||
| 151 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { | 214 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { |
| 152 | sockaddr_in result; | 215 | sockaddr_in result; |
| @@ -198,7 +261,7 @@ bool EnableNonBlock(int fd, bool enable) { | |||
| 198 | return fcntl(fd, F_SETFL, flags) == 0; | 261 | return fcntl(fd, F_SETFL, flags) == 0; |
| 199 | } | 262 | } |
| 200 | 263 | ||
| 201 | Errno TranslateNativeError(int e) { | 264 | Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { |
| 202 | switch (e) { | 265 | switch (e) { |
| 203 | case 0: | 266 | case 0: |
| 204 | return Errno::SUCCESS; | 267 | return Errno::SUCCESS; |
| @@ -208,6 +271,10 @@ Errno TranslateNativeError(int e) { | |||
| 208 | return Errno::INVAL; | 271 | return Errno::INVAL; |
| 209 | case EMFILE: | 272 | case EMFILE: |
| 210 | return Errno::MFILE; | 273 | return Errno::MFILE; |
| 274 | case EPIPE: | ||
| 275 | return Errno::PIPE; | ||
| 276 | case ECONNABORTED: | ||
| 277 | return Errno::CONNABORTED; | ||
| 211 | case ENOTCONN: | 278 | case ENOTCONN: |
| 212 | return Errno::NOTCONN; | 279 | return Errno::NOTCONN; |
| 213 | case EAGAIN: | 280 | case EAGAIN: |
| @@ -236,13 +303,13 @@ Errno TranslateNativeError(int e) { | |||
| 236 | 303 | ||
| 237 | #endif | 304 | #endif |
| 238 | 305 | ||
| 239 | Errno GetAndLogLastError() { | 306 | Errno GetAndLogLastError(CallType call_type = CallType::Other) { |
| 240 | #ifdef _WIN32 | 307 | #ifdef _WIN32 |
| 241 | int e = WSAGetLastError(); | 308 | int e = WSAGetLastError(); |
| 242 | #else | 309 | #else |
| 243 | int e = errno; | 310 | int e = errno; |
| 244 | #endif | 311 | #endif |
| 245 | const Errno err = TranslateNativeError(e); | 312 | const Errno err = TranslateNativeError(e, call_type); |
| 246 | if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { | 313 | if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { |
| 247 | // These happen during normal operation, so only log them at debug level. | 314 | // These happen during normal operation, so only log them at debug level. |
| 248 | LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); | 315 | LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); |
| @@ -473,10 +540,24 @@ NetworkInstance::~NetworkInstance() { | |||
| 473 | Finalize(); | 540 | Finalize(); |
| 474 | } | 541 | } |
| 475 | 542 | ||
| 543 | void CancelPendingSocketOperations() { | ||
| 544 | InterruptSocketOperations(); | ||
| 545 | } | ||
| 546 | |||
| 547 | void RestartSocketOperations() { | ||
| 548 | AcknowledgeInterrupt(); | ||
| 549 | } | ||
| 550 | |||
| 476 | std::optional<IPv4Address> GetHostIPv4Address() { | 551 | std::optional<IPv4Address> GetHostIPv4Address() { |
| 477 | const auto network_interface = Network::GetSelectedNetworkInterface(); | 552 | const auto network_interface = Network::GetSelectedNetworkInterface(); |
| 478 | if (!network_interface.has_value()) { | 553 | if (!network_interface.has_value()) { |
| 479 | LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); | 554 | // Only print the error once to avoid log spam |
| 555 | static bool print_error = true; | ||
| 556 | if (print_error) { | ||
| 557 | LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); | ||
| 558 | print_error = false; | ||
| 559 | } | ||
| 560 | |||
| 480 | return {}; | 561 | return {}; |
| 481 | } | 562 | } |
| 482 | 563 | ||
| @@ -537,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { | |||
| 537 | return result; | 618 | return result; |
| 538 | }); | 619 | }); |
| 539 | 620 | ||
| 540 | const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); | 621 | host_pollfds.push_back(WSAPOLLFD{ |
| 622 | .fd = GetInterruptSocket(), | ||
| 623 | .events = POLLIN, | ||
| 624 | .revents = 0, | ||
| 625 | }); | ||
| 626 | |||
| 627 | const int result = | ||
| 628 | WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout); | ||
| 541 | if (result == 0) { | 629 | if (result == 0) { |
| 542 | ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), | 630 | ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), |
| 543 | [](WSAPOLLFD fd) { return fd.revents == 0; })); | 631 | [](WSAPOLLFD fd) { return fd.revents == 0; })); |
| @@ -604,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { | |||
| 604 | std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { | 692 | std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { |
| 605 | sockaddr_in addr; | 693 | sockaddr_in addr; |
| 606 | socklen_t addrlen = sizeof(addr); | 694 | socklen_t addrlen = sizeof(addr); |
| 695 | |||
| 696 | std::vector<WSAPOLLFD> host_pollfds{ | ||
| 697 | WSAPOLLFD{fd, POLLIN, 0}, | ||
| 698 | WSAPOLLFD{GetInterruptSocket(), POLLIN, 0}, | ||
| 699 | }; | ||
| 700 | |||
| 701 | while (true) { | ||
| 702 | const int pollres = | ||
| 703 | WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1); | ||
| 704 | if (host_pollfds[1].revents != 0) { | ||
| 705 | // Interrupt signaled before a client could be accepted, break | ||
| 706 | return {AcceptResult{}, Errno::AGAIN}; | ||
| 707 | } | ||
| 708 | if (pollres > 0) { | ||
| 709 | break; | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 607 | const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); | 713 | const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); |
| 608 | 714 | ||
| 609 | if (new_socket == INVALID_SOCKET) { | 715 | if (new_socket == INVALID_SOCKET) { |
| @@ -725,13 +831,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { | |||
| 725 | ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | 831 | ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); |
| 726 | ASSERT(flags == 0); | 832 | ASSERT(flags == 0); |
| 727 | 833 | ||
| 834 | int native_flags = 0; | ||
| 835 | #if YUZU_UNIX | ||
| 836 | native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE | ||
| 837 | #endif | ||
| 728 | const auto result = send(fd, reinterpret_cast<const char*>(message.data()), | 838 | const auto result = send(fd, reinterpret_cast<const char*>(message.data()), |
| 729 | static_cast<int>(message.size()), 0); | 839 | static_cast<int>(message.size()), native_flags); |
| 730 | if (result != SOCKET_ERROR) { | 840 | if (result != SOCKET_ERROR) { |
| 731 | return {static_cast<s32>(result), Errno::SUCCESS}; | 841 | return {static_cast<s32>(result), Errno::SUCCESS}; |
| 732 | } | 842 | } |
| 733 | 843 | ||
| 734 | return {-1, GetAndLogLastError()}; | 844 | return {-1, GetAndLogLastError(CallType::Send)}; |
| 735 | } | 845 | } |
| 736 | 846 | ||
| 737 | std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | 847 | std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, |
| @@ -753,7 +863,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | |||
| 753 | return {static_cast<s32>(result), Errno::SUCCESS}; | 863 | return {static_cast<s32>(result), Errno::SUCCESS}; |
| 754 | } | 864 | } |
| 755 | 865 | ||
| 756 | return {-1, GetAndLogLastError()}; | 866 | return {-1, GetAndLogLastError(CallType::Send)}; |
| 757 | } | 867 | } |
| 758 | 868 | ||
| 759 | Errno Socket::Close() { | 869 | Errno Socket::Close() { |
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index badcb8369..b7b7d773a 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h | |||
| @@ -33,10 +33,12 @@ enum class Errno { | |||
| 33 | BADF, | 33 | BADF, |
| 34 | INVAL, | 34 | INVAL, |
| 35 | MFILE, | 35 | MFILE, |
| 36 | PIPE, | ||
| 36 | NOTCONN, | 37 | NOTCONN, |
| 37 | AGAIN, | 38 | AGAIN, |
| 38 | CONNREFUSED, | 39 | CONNREFUSED, |
| 39 | CONNRESET, | 40 | CONNRESET, |
| 41 | CONNABORTED, | ||
| 40 | HOSTUNREACH, | 42 | HOSTUNREACH, |
| 41 | NETDOWN, | 43 | NETDOWN, |
| 42 | NETUNREACH, | 44 | NETUNREACH, |
| @@ -94,6 +96,9 @@ public: | |||
| 94 | ~NetworkInstance(); | 96 | ~NetworkInstance(); |
| 95 | }; | 97 | }; |
| 96 | 98 | ||
| 99 | void CancelPendingSocketOperations(); | ||
| 100 | void RestartSocketOperations(); | ||
| 101 | |||
| 97 | #ifdef _WIN32 | 102 | #ifdef _WIN32 |
| 98 | constexpr IPv4Address TranslateIPv4(in_addr addr) { | 103 | constexpr IPv4Address TranslateIPv4(in_addr addr) { |
| 99 | auto& bytes = addr.S_un.S_un_b; | 104 | auto& bytes = addr.S_un.S_un_b; |
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 4c909a6d3..7c37f660b 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp | |||
| @@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { | |||
| 200 | }); | 200 | }); |
| 201 | 201 | ||
| 202 | if (res == network_interfaces.end()) { | 202 | if (res == network_interfaces.end()) { |
| 203 | LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); | 203 | // Only print the error once to avoid log spam |
| 204 | static bool print_error = true; | ||
| 205 | if (print_error) { | ||
| 206 | LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", | ||
| 207 | selected_network_interface); | ||
| 208 | print_error = false; | ||
| 209 | } | ||
| 210 | |||
| 204 | return std::nullopt; | 211 | return std::nullopt; |
| 205 | } | 212 | } |
| 206 | 213 | ||
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index e04ad19db..5a42dea48 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp | |||
| @@ -18,7 +18,7 @@ namespace Loader { | |||
| 18 | 18 | ||
| 19 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, | 19 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, |
| 20 | bool override_update_) | 20 | bool override_update_) |
| 21 | : AppLoader(std::move(file_)), override_update(override_update_) { | 21 | : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) { |
| 22 | const auto file_dir = file->GetContainingDirectory(); | 22 | const auto file_dir = file->GetContainingDirectory(); |
| 23 | 23 | ||
| 24 | // Title ID | 24 | // Title ID |
| @@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys | |||
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( | 71 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( |
| 72 | FileSys::VirtualDir directory, bool override_update_) | 72 | FileSys::VirtualDir directory, bool override_update_, bool is_hbl_) |
| 73 | : AppLoader(directory->GetFile("main")), dir(std::move(directory)), | 73 | : AppLoader(directory->GetFile("main")), dir(std::move(directory)), |
| 74 | override_update(override_update_) {} | 74 | override_update(override_update_), is_hbl(is_hbl_) {} |
| 75 | 75 | ||
| 76 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { | 76 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { |
| 77 | if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { | 77 | if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { |
| @@ -147,13 +147,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect | |||
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | // Setup the process code layout | 149 | // Setup the process code layout |
| 150 | if (process.LoadFromMetadata(metadata, code_size).IsError()) { | 150 | if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) { |
| 151 | return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; | 151 | return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; |
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | // Load NSO modules | 154 | // Load NSO modules |
| 155 | modules.clear(); | 155 | modules.clear(); |
| 156 | const VAddr base_address{GetInteger(process.GetPageTable().GetCodeRegionStart())}; | 156 | const VAddr base_address{GetInteger(process.GetEntryPoint())}; |
| 157 | VAddr next_load_addr{base_address}; | 157 | VAddr next_load_addr{base_address}; |
| 158 | const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), | 158 | const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), |
| 159 | system.GetContentProvider()}; | 159 | system.GetContentProvider()}; |
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index f7702225e..1e9f765c9 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h | |||
| @@ -27,7 +27,8 @@ public: | |||
| 27 | 27 | ||
| 28 | // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' | 28 | // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' |
| 29 | explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, | 29 | explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, |
| 30 | bool override_update_ = false); | 30 | bool override_update_ = false, |
| 31 | bool is_hbl_ = false); | ||
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * Identifies whether or not the given file is a deconstructed ROM directory. | 34 | * Identifies whether or not the given file is a deconstructed ROM directory. |
| @@ -62,6 +63,7 @@ private: | |||
| 62 | std::string name; | 63 | std::string name; |
| 63 | u64 title_id{}; | 64 | u64 title_id{}; |
| 64 | bool override_update; | 65 | bool override_update; |
| 66 | bool is_hbl; | ||
| 65 | 67 | ||
| 66 | Modules modules; | 68 | Modules modules; |
| 67 | }; | 69 | }; |
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index ffe976b94..bf56a08b4 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp | |||
| @@ -90,13 +90,14 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process, | |||
| 90 | codeset.DataSegment().size += kip->GetBSSSize(); | 90 | codeset.DataSegment().size += kip->GetBSSSize(); |
| 91 | 91 | ||
| 92 | // Setup the process code layout | 92 | // Setup the process code layout |
| 93 | if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) | 93 | if (process |
| 94 | .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) | ||
| 94 | .IsError()) { | 95 | .IsError()) { |
| 95 | return {ResultStatus::ErrorNotInitialized, {}}; | 96 | return {ResultStatus::ErrorNotInitialized, {}}; |
| 96 | } | 97 | } |
| 97 | 98 | ||
| 98 | codeset.memory = std::move(program_image); | 99 | codeset.memory = std::move(program_image); |
| 99 | const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); | 100 | const VAddr base_address = GetInteger(process.GetEntryPoint()); |
| 100 | process.LoadModule(std::move(codeset), base_address); | 101 | process.LoadModule(std::move(codeset), base_address); |
| 101 | 102 | ||
| 102 | LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); | 103 | LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); |
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index f24474ed8..b6e355622 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) { | |||
| 108 | return "unknown"; | 108 | return "unknown"; |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | constexpr std::array<const char*, 66> RESULT_MESSAGES{ | 111 | constexpr std::array<const char*, 68> RESULT_MESSAGES{ |
| 112 | "The operation completed successfully.", | 112 | "The operation completed successfully.", |
| 113 | "The loader requested to load is already loaded.", | 113 | "The loader requested to load is already loaded.", |
| 114 | "The operation is not implemented.", | 114 | "The operation is not implemented.", |
| @@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", | 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", |
| 136 | "The XCI file is missing a Program-type NCA.", | 136 | "The XCI file is missing a Program-type NCA.", |
| 137 | "The NCA file is not an application.", | 137 | "The NCA file is not an application.", |
| 138 | "The ExeFS partition could not be found.", | 138 | "The Program-type NCA contains no executable. An update may be required.", |
| 139 | "The XCI file has a bad header.", | 139 | "The XCI file has a bad header.", |
| 140 | "The XCI file is missing a partition.", | 140 | "The XCI file is missing a partition.", |
| 141 | "The file could not be found or does not exist.", | 141 | "The file could not be found or does not exist.", |
| @@ -169,12 +169,14 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 169 | "The BKTR-type NCA has a bad Subsection block.", | 169 | "The BKTR-type NCA has a bad Subsection block.", |
| 170 | "The BKTR-type NCA has a bad Relocation bucket.", | 170 | "The BKTR-type NCA has a bad Relocation bucket.", |
| 171 | "The BKTR-type NCA has a bad Subsection bucket.", | 171 | "The BKTR-type NCA has a bad Subsection bucket.", |
| 172 | "The BKTR-type NCA is missing the base RomFS.", | 172 | "Game updates cannot be loaded directly. Load the base game instead.", |
| 173 | "The NSP or XCI does not contain an update in addition to the base game.", | 173 | "The NSP or XCI does not contain an update in addition to the base game.", |
| 174 | "The KIP file has a bad header.", | 174 | "The KIP file has a bad header.", |
| 175 | "The KIP BLZ decompression of the section failed unexpectedly.", | 175 | "The KIP BLZ decompression of the section failed unexpectedly.", |
| 176 | "The INI file has a bad header.", | 176 | "The INI file has a bad header.", |
| 177 | "The INI file contains more than the maximum allowable number of KIP files.", | 177 | "The INI file contains more than the maximum allowable number of KIP files.", |
| 178 | "Integrity verification could not be performed for this file.", | ||
| 179 | "Integrity verification failed.", | ||
| 178 | }; | 180 | }; |
| 179 | 181 | ||
| 180 | std::string GetResultStatusString(ResultStatus status) { | 182 | std::string GetResultStatusString(ResultStatus status) { |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7a2a52fd4..b4828f7cd 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <functional> | ||
| 6 | #include <iosfwd> | 7 | #include <iosfwd> |
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <optional> | 9 | #include <optional> |
| @@ -79,8 +80,6 @@ enum class ResultStatus : u16 { | |||
| 79 | ErrorBadPFSHeader, | 80 | ErrorBadPFSHeader, |
| 80 | ErrorIncorrectPFSFileSize, | 81 | ErrorIncorrectPFSFileSize, |
| 81 | ErrorBadNCAHeader, | 82 | ErrorBadNCAHeader, |
| 82 | ErrorCompressedNCA, | ||
| 83 | ErrorSparseNCA, | ||
| 84 | ErrorMissingProductionKeyFile, | 83 | ErrorMissingProductionKeyFile, |
| 85 | ErrorMissingHeaderKey, | 84 | ErrorMissingHeaderKey, |
| 86 | ErrorIncorrectHeaderKey, | 85 | ErrorIncorrectHeaderKey, |
| @@ -134,6 +133,8 @@ enum class ResultStatus : u16 { | |||
| 134 | ErrorBLZDecompressionFailed, | 133 | ErrorBLZDecompressionFailed, |
| 135 | ErrorBadINIHeader, | 134 | ErrorBadINIHeader, |
| 136 | ErrorINITooManyKIPs, | 135 | ErrorINITooManyKIPs, |
| 136 | ErrorIntegrityVerificationNotImplemented, | ||
| 137 | ErrorIntegrityVerificationFailed, | ||
| 137 | }; | 138 | }; |
| 138 | 139 | ||
| 139 | std::string GetResultStatusString(ResultStatus status); | 140 | std::string GetResultStatusString(ResultStatus status); |
| @@ -172,6 +173,13 @@ public: | |||
| 172 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; | 173 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; |
| 173 | 174 | ||
| 174 | /** | 175 | /** |
| 176 | * Try to verify the integrity of the file. | ||
| 177 | */ | ||
| 178 | virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 179 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 180 | } | ||
| 181 | |||
| 182 | /** | ||
| 175 | * Get the code (typically .code section) of the application | 183 | * Get the code (typically .code section) of the application |
| 176 | * | 184 | * |
| 177 | * @param[out] buffer Reference to buffer to store data | 185 | * @param[out] buffer Reference to buffer to store data |
| @@ -276,16 +284,6 @@ public: | |||
| 276 | } | 284 | } |
| 277 | 285 | ||
| 278 | /** | 286 | /** |
| 279 | * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) | ||
| 280 | * data. Needed for BKTR patching. | ||
| 281 | * | ||
| 282 | * @return IVFC offset for RomFS. | ||
| 283 | */ | ||
| 284 | virtual u64 ReadRomFSIVFCOffset() const { | ||
| 285 | return 0; | ||
| 286 | } | ||
| 287 | |||
| 288 | /** | ||
| 289 | * Get the title of the application | 287 | * Get the title of the application |
| 290 | * | 288 | * |
| 291 | * @param[out] title Reference to store the application title into | 289 | * @param[out] title Reference to store the application title into |
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index cf35b1249..3b7b005ff 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp | |||
| @@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 76 | return nca_loader->ReadRomFS(dir); | 76 | return nca_loader->ReadRomFS(dir); |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { | ||
| 80 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 81 | } | ||
| 82 | |||
| 83 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { | 79 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { |
| 84 | return nca_loader->ReadProgramId(out_program_id); | 80 | return nca_loader->ReadProgramId(out_program_id); |
| 85 | } | 81 | } |
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index d7f70db43..81df2bbcd 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h | |||
| @@ -39,7 +39,6 @@ public: | |||
| 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 40 | 40 | ||
| 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 42 | u64 ReadRomFSIVFCOffset() const override; | ||
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 42 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | 43 | ||
| 45 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 44 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 513af194d..4feb6968a 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -3,13 +3,18 @@ | |||
| 3 | 3 | ||
| 4 | #include <utility> | 4 | #include <utility> |
| 5 | 5 | ||
| 6 | #include "common/hex_util.h" | ||
| 7 | #include "common/scope_exit.h" | ||
| 6 | #include "core/core.h" | 8 | #include "core/core.h" |
| 7 | #include "core/file_sys/content_archive.h" | 9 | #include "core/file_sys/content_archive.h" |
| 10 | #include "core/file_sys/nca_metadata.h" | ||
| 11 | #include "core/file_sys/registered_cache.h" | ||
| 8 | #include "core/file_sys/romfs_factory.h" | 12 | #include "core/file_sys/romfs_factory.h" |
| 9 | #include "core/hle/kernel/k_process.h" | 13 | #include "core/hle/kernel/k_process.h" |
| 10 | #include "core/hle/service/filesystem/filesystem.h" | 14 | #include "core/hle/service/filesystem/filesystem.h" |
| 11 | #include "core/loader/deconstructed_rom_directory.h" | 15 | #include "core/loader/deconstructed_rom_directory.h" |
| 12 | #include "core/loader/nca.h" | 16 | #include "core/loader/nca.h" |
| 17 | #include "mbedtls/sha256.h" | ||
| 13 | 18 | ||
| 14 | namespace Loader { | 19 | namespace Loader { |
| 15 | 20 | ||
| @@ -43,9 +48,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 43 | return {ResultStatus::ErrorNCANotProgram, {}}; | 48 | return {ResultStatus::ErrorNCANotProgram, {}}; |
| 44 | } | 49 | } |
| 45 | 50 | ||
| 46 | const auto exefs = nca->GetExeFS(); | 51 | auto exefs = nca->GetExeFS(); |
| 47 | if (exefs == nullptr) { | 52 | if (exefs == nullptr) { |
| 48 | return {ResultStatus::ErrorNoExeFS, {}}; | 53 | LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); |
| 54 | |||
| 55 | // This NCA may be a sparse base of an installed title. | ||
| 56 | // Try to fetch the ExeFS from the installed update. | ||
| 57 | const auto& installed = system.GetContentProvider(); | ||
| 58 | const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), | ||
| 59 | FileSys::ContentRecordType::Program); | ||
| 60 | |||
| 61 | if (update_nca) { | ||
| 62 | exefs = update_nca->GetExeFS(); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (exefs == nullptr) { | ||
| 66 | return {ResultStatus::ErrorNoExeFS, {}}; | ||
| 67 | } | ||
| 49 | } | 68 | } |
| 50 | 69 | ||
| 51 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); | 70 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); |
| @@ -64,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 64 | return load_result; | 83 | return load_result; |
| 65 | } | 84 | } |
| 66 | 85 | ||
| 86 | ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 87 | using namespace Common::Literals; | ||
| 88 | |||
| 89 | constexpr size_t NcaFileNameWithHashLength = 36; | ||
| 90 | constexpr size_t NcaFileNameHashLength = 32; | ||
| 91 | constexpr size_t NcaSha256HashLength = 32; | ||
| 92 | constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2; | ||
| 93 | |||
| 94 | // Get the file name. | ||
| 95 | const auto name = file->GetName(); | ||
| 96 | |||
| 97 | // We won't try to verify meta NCAs. | ||
| 98 | if (name.ends_with(".cnmt.nca")) { | ||
| 99 | return ResultStatus::Success; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Check if we can verify this file. NCAs should be named after their hashes. | ||
| 103 | if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) { | ||
| 104 | LOG_WARNING(Loader, "Unable to validate NCA with name {}", name); | ||
| 105 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Get the expected truncated hash of the NCA. | ||
| 109 | const auto input_hash = | ||
| 110 | Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false); | ||
| 111 | |||
| 112 | // Declare buffer to read into. | ||
| 113 | std::vector<u8> buffer(4_MiB); | ||
| 114 | |||
| 115 | // Initialize sha256 verification context. | ||
| 116 | mbedtls_sha256_context ctx; | ||
| 117 | mbedtls_sha256_init(&ctx); | ||
| 118 | mbedtls_sha256_starts_ret(&ctx, 0); | ||
| 119 | |||
| 120 | // Ensure we maintain a clean state on exit. | ||
| 121 | SCOPE_EXIT({ mbedtls_sha256_free(&ctx); }); | ||
| 122 | |||
| 123 | // Declare counters. | ||
| 124 | const size_t total_size = file->GetSize(); | ||
| 125 | size_t processed_size = 0; | ||
| 126 | |||
| 127 | // Begin iterating the file. | ||
| 128 | while (processed_size < total_size) { | ||
| 129 | // Refill the buffer. | ||
| 130 | const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size); | ||
| 131 | const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size); | ||
| 132 | |||
| 133 | // Update the hash function with the buffer contents. | ||
| 134 | mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size); | ||
| 135 | |||
| 136 | // Update counters. | ||
| 137 | processed_size += read_size; | ||
| 138 | |||
| 139 | // Call the progress function. | ||
| 140 | if (!progress_callback(processed_size, total_size)) { | ||
| 141 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | // Finalize context and compute the output hash. | ||
| 146 | std::array<u8, NcaSha256HashLength> output_hash; | ||
| 147 | mbedtls_sha256_finish_ret(&ctx, output_hash.data()); | ||
| 148 | |||
| 149 | // Compare to expected. | ||
| 150 | if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) { | ||
| 151 | LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name); | ||
| 152 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 153 | } | ||
| 154 | |||
| 155 | // File verified. | ||
| 156 | return ResultStatus::Success; | ||
| 157 | } | ||
| 158 | |||
| 67 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | 159 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { |
| 68 | if (nca == nullptr) { | 160 | if (nca == nullptr) { |
| 69 | return ResultStatus::ErrorNotInitialized; | 161 | return ResultStatus::ErrorNotInitialized; |
| @@ -77,14 +169,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 77 | return ResultStatus::Success; | 169 | return ResultStatus::Success; |
| 78 | } | 170 | } |
| 79 | 171 | ||
| 80 | u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { | ||
| 81 | if (nca == nullptr) { | ||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | return nca->GetBaseIVFCOffset(); | ||
| 86 | } | ||
| 87 | |||
| 88 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | 172 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { |
| 89 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { | 173 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { |
| 90 | return ResultStatus::ErrorNotInitialized; | 174 | return ResultStatus::ErrorNotInitialized; |
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index d22d9146e..96779e27f 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h | |||
| @@ -39,8 +39,9 @@ public: | |||
| 39 | 39 | ||
| 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 41 | 41 | ||
| 42 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 43 | |||
| 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 44 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 43 | u64 ReadRomFSIVFCOffset() const override; | ||
| 44 | ResultStatus ReadProgramId(u64& out_program_id) override; | 45 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 45 | 46 | ||
| 46 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 47 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 506808b5d..69f1a54ed 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp | |||
| @@ -196,14 +196,15 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) | |||
| 196 | program_image.resize(static_cast<u32>(program_image.size()) + bss_size); | 196 | program_image.resize(static_cast<u32>(program_image.size()) + bss_size); |
| 197 | 197 | ||
| 198 | // Setup the process code layout | 198 | // Setup the process code layout |
| 199 | if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) | 199 | if (process |
| 200 | .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) | ||
| 200 | .IsError()) { | 201 | .IsError()) { |
| 201 | return false; | 202 | return false; |
| 202 | } | 203 | } |
| 203 | 204 | ||
| 204 | // Load codeset for current process | 205 | // Load codeset for current process |
| 205 | codeset.memory = std::move(program_image); | 206 | codeset.memory = std::move(program_image); |
| 206 | process.LoadModule(std::move(codeset), process.GetPageTable().GetCodeRegionStart()); | 207 | process.LoadModule(std::move(codeset), process.GetEntryPoint()); |
| 207 | 208 | ||
| 208 | return true; | 209 | return true; |
| 209 | } | 210 | } |
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 74cc9579f..1350da8dc 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: | |||
| 127 | } | 127 | } |
| 128 | 128 | ||
| 129 | // Apply patches if necessary | 129 | // Apply patches if necessary |
| 130 | if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { | 130 | const auto name = nso_file.GetName(); |
| 131 | if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) { | ||
| 131 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); | 132 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); |
| 132 | std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); | 133 | std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); |
| 133 | std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), | 134 | std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), |
| 134 | program_image.size()); | 135 | program_image.size()); |
| 135 | 136 | ||
| 136 | pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); | 137 | pi_header = pm->PatchNSO(pi_header, name); |
| 137 | 138 | ||
| 138 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); | 139 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); |
| 139 | } | 140 | } |
| @@ -167,7 +168,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S | |||
| 167 | modules.clear(); | 168 | modules.clear(); |
| 168 | 169 | ||
| 169 | // Load module | 170 | // Load module |
| 170 | const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); | 171 | const VAddr base_address = GetInteger(process.GetEntryPoint()); |
| 171 | if (!LoadModule(process, system, *file, base_address, true, true)) { | 172 | if (!LoadModule(process, system, *file, base_address, true, true)) { |
| 172 | return {ResultStatus::ErrorLoadingNSO, {}}; | 173 | return {ResultStatus::ErrorLoadingNSO, {}}; |
| 173 | } | 174 | } |
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 80663e0e0..f4ab75b77 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp | |||
| @@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, | |||
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | if (nsp->IsExtractedType()) { | 32 | if (nsp->IsExtractedType()) { |
| 33 | secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); | 33 | secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>( |
| 34 | nsp->GetExeFS(), false, file->GetName() == "hbl.nsp"); | ||
| 34 | } else { | 35 | } else { |
| 35 | const auto control_nca = | 36 | const auto control_nca = |
| 36 | nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); | 37 | nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); |
| @@ -117,12 +118,44 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S | |||
| 117 | return result; | 118 | return result; |
| 118 | } | 119 | } |
| 119 | 120 | ||
| 120 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { | 121 | ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { |
| 121 | return secondary_loader->ReadRomFS(out_file); | 122 | // Extracted-type NSPs can't be verified. |
| 123 | if (nsp->IsExtractedType()) { | ||
| 124 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 125 | } | ||
| 126 | |||
| 127 | // Get list of all NCAs. | ||
| 128 | const auto ncas = nsp->GetNCAsCollapsed(); | ||
| 129 | |||
| 130 | size_t total_size = 0; | ||
| 131 | size_t processed_size = 0; | ||
| 132 | |||
| 133 | // Loop over NCAs, collecting the total size to verify. | ||
| 134 | for (const auto& nca : ncas) { | ||
| 135 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 136 | } | ||
| 137 | |||
| 138 | // Loop over NCAs again, verifying each. | ||
| 139 | for (const auto& nca : ncas) { | ||
| 140 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 141 | |||
| 142 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 143 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 144 | }; | ||
| 145 | |||
| 146 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 147 | if (verification_result != ResultStatus::Success) { | ||
| 148 | return verification_result; | ||
| 149 | } | ||
| 150 | |||
| 151 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 152 | } | ||
| 153 | |||
| 154 | return ResultStatus::Success; | ||
| 122 | } | 155 | } |
| 123 | 156 | ||
| 124 | u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { | 157 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 125 | return secondary_loader->ReadRomFSIVFCOffset(); | 158 | return secondary_loader->ReadRomFS(out_file); |
| 126 | } | 159 | } |
| 127 | 160 | ||
| 128 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 161 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 003cc345c..7ce436c67 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h | |||
| @@ -45,8 +45,9 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 53 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index c7b1b3815..12d72c380 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp | |||
| @@ -85,12 +85,42 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S | |||
| 85 | return result; | 85 | return result; |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { | 88 | ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { |
| 89 | return nca_loader->ReadRomFS(out_file); | 89 | // Verify secure partition, as it is the only thing we can process. |
| 90 | auto secure_partition = xci->GetSecurePartitionNSP(); | ||
| 91 | |||
| 92 | // Get list of all NCAs. | ||
| 93 | const auto ncas = secure_partition->GetNCAsCollapsed(); | ||
| 94 | |||
| 95 | size_t total_size = 0; | ||
| 96 | size_t processed_size = 0; | ||
| 97 | |||
| 98 | // Loop over NCAs, collecting the total size to verify. | ||
| 99 | for (const auto& nca : ncas) { | ||
| 100 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Loop over NCAs again, verifying each. | ||
| 104 | for (const auto& nca : ncas) { | ||
| 105 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 106 | |||
| 107 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 108 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 109 | }; | ||
| 110 | |||
| 111 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 112 | if (verification_result != ResultStatus::Success) { | ||
| 113 | return verification_result; | ||
| 114 | } | ||
| 115 | |||
| 116 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 117 | } | ||
| 118 | |||
| 119 | return ResultStatus::Success; | ||
| 90 | } | 120 | } |
| 91 | 121 | ||
| 92 | u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { | 122 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 93 | return nca_loader->ReadRomFSIVFCOffset(); | 123 | return nca_loader->ReadRomFS(out_file); |
| 94 | } | 124 | } |
| 95 | 125 | ||
| 96 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 126 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 2affb6c6e..b02e136d3 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h | |||
| @@ -45,8 +45,9 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 53 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/core/memory.h b/src/core/memory.h index 2eb61ffd3..13047a545 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -509,9 +509,9 @@ class GuestMemory { | |||
| 509 | 509 | ||
| 510 | public: | 510 | public: |
| 511 | GuestMemory() = delete; | 511 | GuestMemory() = delete; |
| 512 | explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_, | 512 | explicit GuestMemory(M& memory, u64 addr, std::size_t size, |
| 513 | Common::ScratchBuffer<T>* backup = nullptr) | 513 | Common::ScratchBuffer<T>* backup = nullptr) |
| 514 | : memory{memory_}, addr{addr_}, size{size_} { | 514 | : m_memory{memory}, m_addr{addr}, m_size{size} { |
| 515 | static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); | 515 | static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); |
| 516 | if constexpr (FLAGS & GuestMemoryFlags::Read) { | 516 | if constexpr (FLAGS & GuestMemoryFlags::Read) { |
| 517 | Read(addr, size, backup); | 517 | Read(addr, size, backup); |
| @@ -521,89 +521,97 @@ public: | |||
| 521 | ~GuestMemory() = default; | 521 | ~GuestMemory() = default; |
| 522 | 522 | ||
| 523 | T* data() noexcept { | 523 | T* data() noexcept { |
| 524 | return data_span.data(); | 524 | return m_data_span.data(); |
| 525 | } | 525 | } |
| 526 | 526 | ||
| 527 | const T* data() const noexcept { | 527 | const T* data() const noexcept { |
| 528 | return data_span.data(); | 528 | return m_data_span.data(); |
| 529 | } | ||
| 530 | |||
| 531 | size_t size() const noexcept { | ||
| 532 | return m_size; | ||
| 533 | } | ||
| 534 | |||
| 535 | size_t size_bytes() const noexcept { | ||
| 536 | return this->size() * sizeof(T); | ||
| 529 | } | 537 | } |
| 530 | 538 | ||
| 531 | [[nodiscard]] T* begin() noexcept { | 539 | [[nodiscard]] T* begin() noexcept { |
| 532 | return data(); | 540 | return this->data(); |
| 533 | } | 541 | } |
| 534 | 542 | ||
| 535 | [[nodiscard]] const T* begin() const noexcept { | 543 | [[nodiscard]] const T* begin() const noexcept { |
| 536 | return data(); | 544 | return this->data(); |
| 537 | } | 545 | } |
| 538 | 546 | ||
| 539 | [[nodiscard]] T* end() noexcept { | 547 | [[nodiscard]] T* end() noexcept { |
| 540 | return data() + size; | 548 | return this->data() + this->size(); |
| 541 | } | 549 | } |
| 542 | 550 | ||
| 543 | [[nodiscard]] const T* end() const noexcept { | 551 | [[nodiscard]] const T* end() const noexcept { |
| 544 | return data() + size; | 552 | return this->data() + this->size(); |
| 545 | } | 553 | } |
| 546 | 554 | ||
| 547 | T& operator[](size_t index) noexcept { | 555 | T& operator[](size_t index) noexcept { |
| 548 | return data_span[index]; | 556 | return m_data_span[index]; |
| 549 | } | 557 | } |
| 550 | 558 | ||
| 551 | const T& operator[](size_t index) const noexcept { | 559 | const T& operator[](size_t index) const noexcept { |
| 552 | return data_span[index]; | 560 | return m_data_span[index]; |
| 553 | } | 561 | } |
| 554 | 562 | ||
| 555 | void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept { | 563 | void SetAddressAndSize(u64 addr, std::size_t size) noexcept { |
| 556 | addr = addr_; | 564 | m_addr = addr; |
| 557 | size = size_; | 565 | m_size = size; |
| 558 | addr_changed = true; | 566 | m_addr_changed = true; |
| 559 | } | 567 | } |
| 560 | 568 | ||
| 561 | std::span<T> Read(u64 addr_, std::size_t size_, | 569 | std::span<T> Read(u64 addr, std::size_t size, |
| 562 | Common::ScratchBuffer<T>* backup = nullptr) noexcept { | 570 | Common::ScratchBuffer<T>* backup = nullptr) noexcept { |
| 563 | addr = addr_; | 571 | m_addr = addr; |
| 564 | size = size_; | 572 | m_size = size; |
| 565 | if (size == 0) { | 573 | if (m_size == 0) { |
| 566 | is_data_copy = true; | 574 | m_is_data_copy = true; |
| 567 | return {}; | 575 | return {}; |
| 568 | } | 576 | } |
| 569 | 577 | ||
| 570 | if (TrySetSpan()) { | 578 | if (this->TrySetSpan()) { |
| 571 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 579 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 572 | memory.FlushRegion(addr, size * sizeof(T)); | 580 | m_memory.FlushRegion(m_addr, this->size_bytes()); |
| 573 | } | 581 | } |
| 574 | } else { | 582 | } else { |
| 575 | if (backup) { | 583 | if (backup) { |
| 576 | backup->resize_destructive(size); | 584 | backup->resize_destructive(this->size()); |
| 577 | data_span = *backup; | 585 | m_data_span = *backup; |
| 578 | } else { | 586 | } else { |
| 579 | data_copy.resize(size); | 587 | m_data_copy.resize(this->size()); |
| 580 | data_span = std::span(data_copy); | 588 | m_data_span = std::span(m_data_copy); |
| 581 | } | 589 | } |
| 582 | is_data_copy = true; | 590 | m_is_data_copy = true; |
| 583 | span_valid = true; | 591 | m_span_valid = true; |
| 584 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 592 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 585 | memory.ReadBlock(addr, data_span.data(), size * sizeof(T)); | 593 | m_memory.ReadBlock(m_addr, this->data(), this->size_bytes()); |
| 586 | } else { | 594 | } else { |
| 587 | memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T)); | 595 | m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes()); |
| 588 | } | 596 | } |
| 589 | } | 597 | } |
| 590 | return data_span; | 598 | return m_data_span; |
| 591 | } | 599 | } |
| 592 | 600 | ||
| 593 | void Write(std::span<T> write_data) noexcept { | 601 | void Write(std::span<T> write_data) noexcept { |
| 594 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { | 602 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { |
| 595 | memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T)); | 603 | m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes()); |
| 596 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 604 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 597 | memory.WriteBlock(addr, write_data.data(), size * sizeof(T)); | 605 | m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes()); |
| 598 | } else { | 606 | } else { |
| 599 | memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T)); | 607 | m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes()); |
| 600 | } | 608 | } |
| 601 | } | 609 | } |
| 602 | 610 | ||
| 603 | bool TrySetSpan() noexcept { | 611 | bool TrySetSpan() noexcept { |
| 604 | if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) { | 612 | if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) { |
| 605 | data_span = {reinterpret_cast<T*>(ptr), size}; | 613 | m_data_span = {reinterpret_cast<T*>(ptr), this->size()}; |
| 606 | span_valid = true; | 614 | m_span_valid = true; |
| 607 | return true; | 615 | return true; |
| 608 | } | 616 | } |
| 609 | return false; | 617 | return false; |
| @@ -611,36 +619,36 @@ public: | |||
| 611 | 619 | ||
| 612 | protected: | 620 | protected: |
| 613 | bool IsDataCopy() const noexcept { | 621 | bool IsDataCopy() const noexcept { |
| 614 | return is_data_copy; | 622 | return m_is_data_copy; |
| 615 | } | 623 | } |
| 616 | 624 | ||
| 617 | bool AddressChanged() const noexcept { | 625 | bool AddressChanged() const noexcept { |
| 618 | return addr_changed; | 626 | return m_addr_changed; |
| 619 | } | 627 | } |
| 620 | 628 | ||
| 621 | M& memory; | 629 | M& m_memory; |
| 622 | u64 addr; | 630 | u64 m_addr{}; |
| 623 | size_t size; | 631 | size_t m_size{}; |
| 624 | std::span<T> data_span{}; | 632 | std::span<T> m_data_span{}; |
| 625 | std::vector<T> data_copy; | 633 | std::vector<T> m_data_copy{}; |
| 626 | bool span_valid{false}; | 634 | bool m_span_valid{false}; |
| 627 | bool is_data_copy{false}; | 635 | bool m_is_data_copy{false}; |
| 628 | bool addr_changed{false}; | 636 | bool m_addr_changed{false}; |
| 629 | }; | 637 | }; |
| 630 | 638 | ||
| 631 | template <typename M, typename T, GuestMemoryFlags FLAGS> | 639 | template <typename M, typename T, GuestMemoryFlags FLAGS> |
| 632 | class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { | 640 | class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { |
| 633 | public: | 641 | public: |
| 634 | GuestMemoryScoped() = delete; | 642 | GuestMemoryScoped() = delete; |
| 635 | explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_, | 643 | explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size, |
| 636 | Common::ScratchBuffer<T>* backup = nullptr) | 644 | Common::ScratchBuffer<T>* backup = nullptr) |
| 637 | : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) { | 645 | : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) { |
| 638 | if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { | 646 | if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { |
| 639 | if (!this->TrySetSpan()) { | 647 | if (!this->TrySetSpan()) { |
| 640 | if (backup) { | 648 | if (backup) { |
| 641 | this->data_span = *backup; | 649 | this->m_data_span = *backup; |
| 642 | this->span_valid = true; | 650 | this->m_span_valid = true; |
| 643 | this->is_data_copy = true; | 651 | this->m_is_data_copy = true; |
| 644 | } | 652 | } |
| 645 | } | 653 | } |
| 646 | } | 654 | } |
| @@ -648,24 +656,21 @@ public: | |||
| 648 | 656 | ||
| 649 | ~GuestMemoryScoped() { | 657 | ~GuestMemoryScoped() { |
| 650 | if constexpr (FLAGS & GuestMemoryFlags::Write) { | 658 | if constexpr (FLAGS & GuestMemoryFlags::Write) { |
| 651 | if (this->size == 0) [[unlikely]] { | 659 | if (this->size() == 0) [[unlikely]] { |
| 652 | return; | 660 | return; |
| 653 | } | 661 | } |
| 654 | 662 | ||
| 655 | if (this->AddressChanged() || this->IsDataCopy()) { | 663 | if (this->AddressChanged() || this->IsDataCopy()) { |
| 656 | ASSERT(this->span_valid); | 664 | ASSERT(this->m_span_valid); |
| 657 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { | 665 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { |
| 658 | this->memory.WriteBlockCached(this->addr, this->data_span.data(), | 666 | this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes()); |
| 659 | this->size * sizeof(T)); | ||
| 660 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 667 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 661 | this->memory.WriteBlock(this->addr, this->data_span.data(), | 668 | this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes()); |
| 662 | this->size * sizeof(T)); | ||
| 663 | } else { | 669 | } else { |
| 664 | this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(), | 670 | this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes()); |
| 665 | this->size * sizeof(T)); | ||
| 666 | } | 671 | } |
| 667 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 672 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 668 | this->memory.InvalidateRegion(this->addr, this->size * sizeof(T)); | 673 | this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes()); |
| 669 | } | 674 | } |
| 670 | } | 675 | } |
| 671 | } | 676 | } |
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 7b52f61a7..a06e99166 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp | |||
| @@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { | |||
| 154 | return {}; | 154 | return {}; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); | 157 | const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10)); |
| 158 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = | 158 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = |
| 159 | value; | 159 | value; |
| 160 | 160 | ||
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index b5b3e7eda..ed875d444 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp | |||
| @@ -117,8 +117,8 @@ json GetProcessorStateDataAuto(Core::System& system) { | |||
| 117 | arm.SaveContext(context); | 117 | arm.SaveContext(context); |
| 118 | 118 | ||
| 119 | return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", | 119 | return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", |
| 120 | GetInteger(process->GetPageTable().GetCodeRegionStart()), | 120 | GetInteger(process->GetEntryPoint()), context.sp, context.pc, |
| 121 | context.sp, context.pc, context.pstate, context.cpu_registers); | 121 | context.pstate, context.cpu_registers); |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | json GetBacktraceData(Core::System& system) { | 124 | json GetBacktraceData(Core::System& system) { |
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 62b3f6636..c26179e03 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | #include "common/logging/log.h" | 14 | #include "common/logging/log.h" |
| 15 | 15 | ||
| 16 | #include "common/settings.h" | 16 | #include "common/settings.h" |
| 17 | #include "common/settings_enums.h" | ||
| 17 | #include "core/file_sys/control_metadata.h" | 18 | #include "core/file_sys/control_metadata.h" |
| 18 | #include "core/file_sys/patch_manager.h" | 19 | #include "core/file_sys/patch_manager.h" |
| 19 | #include "core/loader/loader.h" | 20 | #include "core/loader/loader.h" |
| @@ -275,7 +276,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, | |||
| 275 | static_cast<u32>(Settings::values.shader_backend.GetValue())); | 276 | static_cast<u32>(Settings::values.shader_backend.GetValue())); |
| 276 | AddField(field_type, "Renderer_UseAsynchronousShaders", | 277 | AddField(field_type, "Renderer_UseAsynchronousShaders", |
| 277 | Settings::values.use_asynchronous_shaders.GetValue()); | 278 | Settings::values.use_asynchronous_shaders.GetValue()); |
| 278 | AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue()); | 279 | AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode()); |
| 279 | } | 280 | } |
| 280 | 281 | ||
| 281 | bool TelemetrySession::SubmitTestcase() { | 282 | bool TelemetrySession::SubmitTestcase() { |
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp new file mode 100644 index 000000000..44d24822a --- /dev/null +++ b/src/core/tools/renderdoc.cpp | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <renderdoc_app.h> | ||
| 5 | |||
| 6 | #include "common/assert.h" | ||
| 7 | #include "common/dynamic_library.h" | ||
| 8 | #include "core/tools/renderdoc.h" | ||
| 9 | |||
| 10 | #ifdef WIN32 | ||
| 11 | #include <windows.h> | ||
| 12 | #else | ||
| 13 | #include <dlfcn.h> | ||
| 14 | #endif | ||
| 15 | |||
| 16 | namespace Tools { | ||
| 17 | |||
| 18 | RenderdocAPI::RenderdocAPI() { | ||
| 19 | #ifdef WIN32 | ||
| 20 | if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { | ||
| 21 | const auto RENDERDOC_GetAPI = | ||
| 22 | reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI")); | ||
| 23 | const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); | ||
| 24 | ASSERT(ret == 1); | ||
| 25 | } | ||
| 26 | #else | ||
| 27 | #ifdef ANDROID | ||
| 28 | static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so"; | ||
| 29 | #else | ||
| 30 | static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so"; | ||
| 31 | #endif | ||
| 32 | if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) { | ||
| 33 | const auto RENDERDOC_GetAPI = | ||
| 34 | reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI")); | ||
| 35 | const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); | ||
| 36 | ASSERT(ret == 1); | ||
| 37 | } | ||
| 38 | #endif | ||
| 39 | } | ||
| 40 | |||
| 41 | RenderdocAPI::~RenderdocAPI() = default; | ||
| 42 | |||
| 43 | void RenderdocAPI::ToggleCapture() { | ||
| 44 | if (!rdoc_api) [[unlikely]] { | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | if (!is_capturing) { | ||
| 48 | rdoc_api->StartFrameCapture(NULL, NULL); | ||
| 49 | } else { | ||
| 50 | rdoc_api->EndFrameCapture(NULL, NULL); | ||
| 51 | } | ||
| 52 | is_capturing = !is_capturing; | ||
| 53 | } | ||
| 54 | |||
| 55 | } // namespace Tools | ||
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h new file mode 100644 index 000000000..0e5e43da5 --- /dev/null +++ b/src/core/tools/renderdoc.h | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | struct RENDERDOC_API_1_6_0; | ||
| 7 | |||
| 8 | namespace Tools { | ||
| 9 | |||
| 10 | class RenderdocAPI { | ||
| 11 | public: | ||
| 12 | explicit RenderdocAPI(); | ||
| 13 | ~RenderdocAPI(); | ||
| 14 | |||
| 15 | void ToggleCapture(); | ||
| 16 | |||
| 17 | private: | ||
| 18 | RENDERDOC_API_1_6_0* rdoc_api{}; | ||
| 19 | bool is_capturing{false}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | } // namespace Tools | ||
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index d707dabe2..93038f161 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp | |||
| @@ -368,9 +368,9 @@ int main(int argc, char** argv) { | |||
| 368 | if (auto room = network.GetRoom().lock()) { | 368 | if (auto room = network.GetRoom().lock()) { |
| 369 | AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, | 369 | AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, |
| 370 | .id = preferred_game_id}; | 370 | .id = preferred_game_id}; |
| 371 | if (!room->Create(room_name, room_description, bind_address, port, password, max_members, | 371 | if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port), |
| 372 | username, preferred_game_info, std::move(verify_backend), ban_list, | 372 | password, max_members, username, preferred_game_info, |
| 373 | enable_yuzu_mods)) { | 373 | std::move(verify_backend), ban_list, enable_yuzu_mods)) { |
| 374 | LOG_INFO(Network, "Failed to create room: "); | 374 | LOG_INFO(Network, "Failed to create room: "); |
| 375 | return -1; | 375 | return -1; |
| 376 | } | 376 | } |
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 322c29065..5c127c8ef 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt | |||
| @@ -37,8 +37,6 @@ add_library(input_common STATIC | |||
| 37 | 37 | ||
| 38 | if (MSVC) | 38 | if (MSVC) |
| 39 | target_compile_options(input_common PRIVATE | 39 | target_compile_options(input_common PRIVATE |
| 40 | /W4 | ||
| 41 | |||
| 42 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data | 40 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data |
| 43 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data | 41 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data |
| 44 | /we4800 # Implicit conversion from 'type' to bool. Possible information loss | 42 | /we4800 # Implicit conversion from 'type' to bool. Possible information loss |
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 870e76ab0..188e862d7 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp | |||
| @@ -835,15 +835,15 @@ public: | |||
| 835 | return input_engine->SupportsNfc(identifier); | 835 | return input_engine->SupportsNfc(identifier); |
| 836 | } | 836 | } |
| 837 | 837 | ||
| 838 | Common::Input::NfcState StartNfcPolling() { | 838 | Common::Input::NfcState StartNfcPolling() override { |
| 839 | return input_engine->StartNfcPolling(identifier); | 839 | return input_engine->StartNfcPolling(identifier); |
| 840 | } | 840 | } |
| 841 | 841 | ||
| 842 | Common::Input::NfcState StopNfcPolling() { | 842 | Common::Input::NfcState StopNfcPolling() override { |
| 843 | return input_engine->StopNfcPolling(identifier); | 843 | return input_engine->StopNfcPolling(identifier); |
| 844 | } | 844 | } |
| 845 | 845 | ||
| 846 | Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) { | 846 | Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) override { |
| 847 | return input_engine->ReadAmiiboData(identifier, out_data); | 847 | return input_engine->ReadAmiiboData(identifier, out_data); |
| 848 | } | 848 | } |
| 849 | 849 | ||
| @@ -852,11 +852,11 @@ public: | |||
| 852 | } | 852 | } |
| 853 | 853 | ||
| 854 | Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request, | 854 | Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request, |
| 855 | Common::Input::MifareRequest& out_data) { | 855 | Common::Input::MifareRequest& out_data) override { |
| 856 | return input_engine->ReadMifareData(identifier, request, out_data); | 856 | return input_engine->ReadMifareData(identifier, request, out_data); |
| 857 | } | 857 | } |
| 858 | 858 | ||
| 859 | Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) { | 859 | Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) override { |
| 860 | return input_engine->WriteMifareData(identifier, request); | 860 | return input_engine->WriteMifareData(identifier, request); |
| 861 | } | 861 | } |
| 862 | 862 | ||
diff --git a/src/network/room.cpp b/src/network/room.cpp index e456ea09c..d87db37de 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp | |||
| @@ -805,7 +805,7 @@ IPv4Address Room::RoomImpl::GenerateFakeIPAddress() { | |||
| 805 | std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE | 805 | std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE |
| 806 | do { | 806 | do { |
| 807 | for (std::size_t i = 2; i < result_ip.size(); ++i) { | 807 | for (std::size_t i = 2; i < result_ip.size(); ++i) { |
| 808 | result_ip[i] = dis(random_gen); | 808 | result_ip[i] = static_cast<u8>(dis(random_gen)); |
| 809 | } | 809 | } |
| 810 | } while (!IsValidFakeIPAddress(result_ip)); | 810 | } while (!IsValidFakeIPAddress(result_ip)); |
| 811 | 811 | ||
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index 07e75f9d8..83b763447 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt | |||
| @@ -245,8 +245,6 @@ target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit) | |||
| 245 | 245 | ||
| 246 | if (MSVC) | 246 | if (MSVC) |
| 247 | target_compile_options(shader_recompiler PRIVATE | 247 | target_compile_options(shader_recompiler PRIVATE |
| 248 | /W4 | ||
| 249 | |||
| 250 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data | 248 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data |
| 251 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data | 249 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data |
| 252 | /we4800 # Implicit conversion from 'type' to bool. Possible information loss | 250 | /we4800 # Implicit conversion from 'type' to bool. Possible information loss |
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp index 85ee27333..d0e308124 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp | |||
| @@ -558,12 +558,15 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 558 | const IR::Value& coord, const IR::Value& derivatives, | 558 | const IR::Value& coord, const IR::Value& derivatives, |
| 559 | const IR::Value& offset, const IR::Value& lod_clamp) { | 559 | const IR::Value& offset, const IR::Value& lod_clamp) { |
| 560 | const auto info{inst.Flags<IR::TextureInstInfo>()}; | 560 | const auto info{inst.Flags<IR::TextureInstInfo>()}; |
| 561 | ScopedRegister dpdx, dpdy; | 561 | ScopedRegister dpdx, dpdy, coords; |
| 562 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; | 562 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; |
| 563 | if (multi_component) { | 563 | if (multi_component) { |
| 564 | // Allocate this early to avoid aliasing other registers | 564 | // Allocate this early to avoid aliasing other registers |
| 565 | dpdx = ScopedRegister{ctx.reg_alloc}; | 565 | dpdx = ScopedRegister{ctx.reg_alloc}; |
| 566 | dpdy = ScopedRegister{ctx.reg_alloc}; | 566 | dpdy = ScopedRegister{ctx.reg_alloc}; |
| 567 | if (info.num_derivates >= 3) { | ||
| 568 | coords = ScopedRegister{ctx.reg_alloc}; | ||
| 569 | } | ||
| 567 | } | 570 | } |
| 568 | const auto sparse_inst{PrepareSparse(inst)}; | 571 | const auto sparse_inst{PrepareSparse(inst)}; |
| 569 | const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""}; | 572 | const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""}; |
| @@ -580,15 +583,27 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 580 | "MOV.F {}.y,{}.w;", | 583 | "MOV.F {}.y,{}.w;", |
| 581 | dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec, | 584 | dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec, |
| 582 | dpdy.reg, derivatives_vec); | 585 | dpdy.reg, derivatives_vec); |
| 586 | Register final_coord; | ||
| 587 | if (info.num_derivates >= 3) { | ||
| 588 | ctx.Add("MOV.F {}.z,{}.x;" | ||
| 589 | "MOV.F {}.z,{}.y;", | ||
| 590 | dpdx.reg, coord_vec, dpdy.reg, coord_vec); | ||
| 591 | ctx.Add("MOV.F {}.x,0;" | ||
| 592 | "MOV.F {}.y,0;", | ||
| 593 | "MOV.F {}.z,0;", coords.reg, coords.reg, coords.reg); | ||
| 594 | final_coord = coords.reg; | ||
| 595 | } else { | ||
| 596 | final_coord = coord_vec; | ||
| 597 | } | ||
| 583 | if (info.has_lod_clamp) { | 598 | if (info.has_lod_clamp) { |
| 584 | const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)}; | 599 | const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)}; |
| 585 | ctx.Add("MOV.F {}.w,{};" | 600 | ctx.Add("MOV.F {}.w,{};" |
| 586 | "TXD.F.LODCLAMP{} {},{},{},{},{},{}{};", | 601 | "TXD.F.LODCLAMP{} {},{},{},{},{},{}{};", |
| 587 | dpdy.reg, lod_clamp_value, sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg, | 602 | dpdy.reg, lod_clamp_value, sparse_mod, ret, final_coord, dpdx.reg, dpdy.reg, |
| 588 | texture, type, offset_vec); | 603 | texture, type, offset_vec); |
| 589 | } else { | 604 | } else { |
| 590 | ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg, | 605 | ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, final_coord, dpdx.reg, |
| 591 | texture, type, offset_vec); | 606 | dpdy.reg, texture, type, offset_vec); |
| 592 | } | 607 | } |
| 593 | } else { | 608 | } else { |
| 594 | ctx.Add("TXD.F{} {},{},{}.x,{}.y,{},{}{};", sparse_mod, ret, coord_vec, derivatives_vec, | 609 | ctx.Add("TXD.F{} {},{},{}.x,{}.y,{},{}{};", sparse_mod, ret, coord_vec, derivatives_vec, |
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp index 418505475..d9872ecc2 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp | |||
| @@ -548,7 +548,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 548 | if (sparse_inst) { | 548 | if (sparse_inst) { |
| 549 | throw NotImplementedException("EmitImageGradient Sparse"); | 549 | throw NotImplementedException("EmitImageGradient Sparse"); |
| 550 | } | 550 | } |
| 551 | if (!offset.IsEmpty()) { | 551 | if (!offset.IsEmpty() && info.num_derivates <= 2) { |
| 552 | throw NotImplementedException("EmitImageGradient offset"); | 552 | throw NotImplementedException("EmitImageGradient offset"); |
| 553 | } | 553 | } |
| 554 | const auto texture{Texture(ctx, info, index)}; | 554 | const auto texture{Texture(ctx, info, index)}; |
| @@ -556,6 +556,12 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 556 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; | 556 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; |
| 557 | const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)}; | 557 | const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)}; |
| 558 | if (multi_component) { | 558 | if (multi_component) { |
| 559 | if (info.num_derivates >= 3) { | ||
| 560 | const auto offset_vec{ctx.var_alloc.Consume(offset)}; | ||
| 561 | ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture, | ||
| 562 | coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec); | ||
| 563 | return; | ||
| 564 | } | ||
| 559 | ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords, | 565 | ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords, |
| 560 | derivatives_vec, derivatives_vec); | 566 | derivatives_vec, derivatives_vec); |
| 561 | } else { | 567 | } else { |
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 7d901c04b..8decdf399 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp | |||
| @@ -91,6 +91,34 @@ public: | |||
| 91 | } | 91 | } |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2, | ||
| 95 | Id offset, Id lod_clamp) { | ||
| 96 | if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) { | ||
| 97 | throw LogicError("Derivates must be present"); | ||
| 98 | } | ||
| 99 | boost::container::static_vector<Id, 3> deriv_1_accum{ | ||
| 100 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 0), | ||
| 101 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 2), | ||
| 102 | ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 0), | ||
| 103 | }; | ||
| 104 | boost::container::static_vector<Id, 3> deriv_2_accum{ | ||
| 105 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 1), | ||
| 106 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 3), | ||
| 107 | ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 1), | ||
| 108 | }; | ||
| 109 | const Id derivates_id1{ctx.OpCompositeConstruct( | ||
| 110 | ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})}; | ||
| 111 | const Id derivates_id2{ctx.OpCompositeConstruct( | ||
| 112 | ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})}; | ||
| 113 | Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2); | ||
| 114 | if (Sirit::ValidId(offset)) { | ||
| 115 | Add(spv::ImageOperandsMask::Offset, offset); | ||
| 116 | } | ||
| 117 | if (has_lod_clamp) { | ||
| 118 | Add(spv::ImageOperandsMask::MinLod, lod_clamp); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 94 | std::span<const Id> Span() const noexcept { | 122 | std::span<const Id> Span() const noexcept { |
| 95 | return std::span{operands.data(), operands.size()}; | 123 | return std::span{operands.data(), operands.size()}; |
| 96 | } | 124 | } |
| @@ -176,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind | |||
| 176 | if (def.count > 1) { | 204 | if (def.count > 1) { |
| 177 | throw NotImplementedException("Indirect texture sample"); | 205 | throw NotImplementedException("Indirect texture sample"); |
| 178 | } | 206 | } |
| 179 | const Id sampler_id{def.id}; | 207 | return ctx.OpLoad(ctx.image_buffer_type, def.id); |
| 180 | const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)}; | ||
| 181 | return ctx.OpImage(ctx.image_buffer_type, id); | ||
| 182 | } else { | 208 | } else { |
| 183 | const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; | 209 | const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; |
| 184 | if (def.count > 1) { | 210 | if (def.count > 1) { |
| @@ -524,8 +550,11 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I | |||
| 524 | Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, | 550 | Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, |
| 525 | Id derivates, Id offset, Id lod_clamp) { | 551 | Id derivates, Id offset, Id lod_clamp) { |
| 526 | const auto info{inst->Flags<IR::TextureInstInfo>()}; | 552 | const auto info{inst->Flags<IR::TextureInstInfo>()}; |
| 527 | const ImageOperands operands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, | 553 | const auto operands = |
| 528 | offset, lod_clamp); | 554 | info.num_derivates == 3 |
| 555 | ? ImageOperands(ctx, info.has_lod_clamp != 0, derivates, offset, {}, lod_clamp) | ||
| 556 | : ImageOperands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, offset, | ||
| 557 | lod_clamp); | ||
| 529 | return Emit(&EmitContext::OpImageSparseSampleExplicitLod, | 558 | return Emit(&EmitContext::OpImageSparseSampleExplicitLod, |
| 530 | &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], | 559 | &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], |
| 531 | Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); | 560 | Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index bec5db173..72f69b7aa 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | |||
| @@ -74,6 +74,11 @@ spv::ImageFormat GetImageFormat(ImageFormat format) { | |||
| 74 | throw InvalidArgument("Invalid image format {}", format); | 74 | throw InvalidArgument("Invalid image format {}", format); |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) { | ||
| 78 | const auto spv_format = GetImageFormat(format); | ||
| 79 | return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format; | ||
| 80 | } | ||
| 81 | |||
| 77 | Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { | 82 | Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { |
| 78 | const spv::ImageFormat format{GetImageFormat(desc.format)}; | 83 | const spv::ImageFormat format{GetImageFormat(desc.format)}; |
| 79 | const Id type{ctx.U32[1]}; | 84 | const Id type{ctx.U32[1]}; |
| @@ -1242,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) { | |||
| 1242 | } | 1247 | } |
| 1243 | const spv::ImageFormat format{spv::ImageFormat::Unknown}; | 1248 | const spv::ImageFormat format{spv::ImageFormat::Unknown}; |
| 1244 | image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); | 1249 | image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); |
| 1245 | sampled_texture_buffer_type = TypeSampledImage(image_buffer_type); | ||
| 1246 | 1250 | ||
| 1247 | const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)}; | 1251 | const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)}; |
| 1248 | texture_buffers.reserve(info.texture_buffer_descriptors.size()); | 1252 | texture_buffers.reserve(info.texture_buffer_descriptors.size()); |
| 1249 | for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { | 1253 | for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { |
| 1250 | if (desc.count != 1) { | 1254 | if (desc.count != 1) { |
| @@ -1271,7 +1275,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) { | |||
| 1271 | if (desc.count != 1) { | 1275 | if (desc.count != 1) { |
| 1272 | throw NotImplementedException("Array of image buffers"); | 1276 | throw NotImplementedException("Array of image buffers"); |
| 1273 | } | 1277 | } |
| 1274 | const spv::ImageFormat format{GetImageFormat(desc.format)}; | 1278 | const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; |
| 1275 | const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; | 1279 | const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; |
| 1276 | const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; | 1280 | const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; |
| 1277 | const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; | 1281 | const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index e63330f11..7c49fd504 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h | |||
| @@ -206,7 +206,6 @@ public: | |||
| 206 | Id output_u32{}; | 206 | Id output_u32{}; |
| 207 | 207 | ||
| 208 | Id image_buffer_type{}; | 208 | Id image_buffer_type{}; |
| 209 | Id sampled_texture_buffer_type{}; | ||
| 210 | Id image_u32{}; | 209 | Id image_u32{}; |
| 211 | 210 | ||
| 212 | std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; | 211 | std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; |
diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h index 69035d462..1e9e8c8f5 100644 --- a/src/shader_recompiler/frontend/ir/modifiers.h +++ b/src/shader_recompiler/frontend/ir/modifiers.h | |||
| @@ -42,6 +42,7 @@ union TextureInstInfo { | |||
| 42 | BitField<23, 2, u32> gather_component; | 42 | BitField<23, 2, u32> gather_component; |
| 43 | BitField<25, 2, u32> num_derivates; | 43 | BitField<25, 2, u32> num_derivates; |
| 44 | BitField<27, 3, ImageFormat> image_format; | 44 | BitField<27, 3, ImageFormat> image_format; |
| 45 | BitField<30, 1, u32> ndv_is_active; | ||
| 45 | }; | 46 | }; |
| 46 | static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); | 47 | static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); |
| 47 | 48 | ||
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp index ef4ffa54b..f00e20023 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp | |||
| @@ -19,7 +19,7 @@ void TranslatorVisitor::FSWZADD(u64 insn) { | |||
| 19 | } const fswzadd{insn}; | 19 | } const fswzadd{insn}; |
| 20 | 20 | ||
| 21 | if (fswzadd.ndv != 0) { | 21 | if (fswzadd.ndv != 0) { |
| 22 | throw NotImplementedException("FSWZADD NDV"); | 22 | LOG_WARNING(Shader, "(STUBBED) FSWZADD - NDV mode"); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | const IR::F32 src_a{GetFloatReg8(insn)}; | 25 | const IR::F32 src_a{GetFloatReg8(insn)}; |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp index 82aec3b73..1ddfeab06 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp | |||
| @@ -16,8 +16,10 @@ void MOV(TranslatorVisitor& v, u64 insn, const IR::U32& src, bool is_mov32i = fa | |||
| 16 | BitField<12, 4, u64> mov32i_mask; | 16 | BitField<12, 4, u64> mov32i_mask; |
| 17 | } const mov{insn}; | 17 | } const mov{insn}; |
| 18 | 18 | ||
| 19 | if ((is_mov32i ? mov.mov32i_mask : mov.mask) != 0xf) { | 19 | u64 mask = is_mov32i ? mov.mov32i_mask : mov.mask; |
| 20 | throw NotImplementedException("Non-full move mask"); | 20 | if (mask != 0xf && mask != 0x1) { |
| 21 | LOG_WARNING(Shader, "(STUBBED) Masked Mov"); | ||
| 22 | return; | ||
| 21 | } | 23 | } |
| 22 | v.X(mov.dest_reg, src); | 24 | v.X(mov.dest_reg, src); |
| 23 | } | 25 | } |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp index 753c62098..e593132e6 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp | |||
| @@ -161,7 +161,8 @@ enum class SpecialRegister : u64 { | |||
| 161 | LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); | 161 | LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); |
| 162 | return ir.Imm32(0); // This is the default value hardware returns. | 162 | return ir.Imm32(0); // This is the default value hardware returns. |
| 163 | default: | 163 | default: |
| 164 | throw NotImplementedException("S2R special register {}", special_register); | 164 | LOG_CRITICAL(Shader, "(STUBBED) Special register {}", special_register); |
| 165 | return ir.Imm32(0); // This is the default value hardware returns. | ||
| 165 | } | 166 | } |
| 166 | } | 167 | } |
| 167 | } // Anonymous namespace | 168 | } // Anonymous namespace |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp index 2f930f1ea..6203003b3 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp | |||
| @@ -209,7 +209,7 @@ void TranslatorVisitor::R2B(u64) { | |||
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | void TranslatorVisitor::RAM(u64) { | 211 | void TranslatorVisitor::RAM(u64) { |
| 212 | ThrowNotImplemented(Opcode::RAM); | 212 | LOG_WARNING(Shader, "(STUBBED) RAM Instruction"); |
| 213 | } | 213 | } |
| 214 | 214 | ||
| 215 | void TranslatorVisitor::RET(u64) { | 215 | void TranslatorVisitor::RET(u64) { |
| @@ -221,7 +221,7 @@ void TranslatorVisitor::RTT(u64) { | |||
| 221 | } | 221 | } |
| 222 | 222 | ||
| 223 | void TranslatorVisitor::SAM(u64) { | 223 | void TranslatorVisitor::SAM(u64) { |
| 224 | ThrowNotImplemented(Opcode::SAM); | 224 | LOG_WARNING(Shader, "(STUBBED) SAM Instruction"); |
| 225 | } | 225 | } |
| 226 | 226 | ||
| 227 | void TranslatorVisitor::SETCRSPTR(u64) { | 227 | void TranslatorVisitor::SETCRSPTR(u64) { |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp index 2459fc30d..7a9b7fff8 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp | |||
| @@ -172,6 +172,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool aoffi, Blod blod, bool lc, | |||
| 172 | info.is_depth.Assign(tex.dc != 0 ? 1 : 0); | 172 | info.is_depth.Assign(tex.dc != 0 ? 1 : 0); |
| 173 | info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0); | 173 | info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0); |
| 174 | info.has_lod_clamp.Assign(lc ? 1 : 0); | 174 | info.has_lod_clamp.Assign(lc ? 1 : 0); |
| 175 | info.ndv_is_active.Assign(tex.ndv != 0 ? 1 : 0); | ||
| 175 | 176 | ||
| 176 | const IR::Value sample{[&]() -> IR::Value { | 177 | const IR::Value sample{[&]() -> IR::Value { |
| 177 | if (tex.dc == 0) { | 178 | if (tex.dc == 0) { |
diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp index 4d81e9336..f46e55122 100644 --- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include "shader_recompiler/environment.h" | 10 | #include "shader_recompiler/environment.h" |
| 11 | #include "shader_recompiler/exception.h" | 11 | #include "shader_recompiler/exception.h" |
| 12 | #include "shader_recompiler/frontend/ir/ir_emitter.h" | 12 | #include "shader_recompiler/frontend/ir/ir_emitter.h" |
| 13 | #include "shader_recompiler/frontend/ir/modifiers.h" | ||
| 13 | #include "shader_recompiler/frontend/ir/value.h" | 14 | #include "shader_recompiler/frontend/ir/value.h" |
| 14 | #include "shader_recompiler/ir_opt/passes.h" | 15 | #include "shader_recompiler/ir_opt/passes.h" |
| 15 | 16 | ||
| @@ -410,7 +411,49 @@ void FoldSelect(IR::Inst& inst) { | |||
| 410 | } | 411 | } |
| 411 | } | 412 | } |
| 412 | 413 | ||
| 414 | void FoldFPAdd32(IR::Inst& inst) { | ||
| 415 | if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a + b; })) { | ||
| 416 | return; | ||
| 417 | } | ||
| 418 | const IR::Value lhs_value{inst.Arg(0)}; | ||
| 419 | const IR::Value rhs_value{inst.Arg(1)}; | ||
| 420 | const auto check_neutral = [](const IR::Value& one_operand) { | ||
| 421 | return one_operand.IsImmediate() && std::abs(one_operand.F32()) == 0.0f; | ||
| 422 | }; | ||
| 423 | if (check_neutral(lhs_value)) { | ||
| 424 | inst.ReplaceUsesWith(rhs_value); | ||
| 425 | } | ||
| 426 | if (check_neutral(rhs_value)) { | ||
| 427 | inst.ReplaceUsesWith(lhs_value); | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | bool FoldDerivateYFromCorrection(IR::Inst& inst) { | ||
| 432 | const IR::Value lhs_value{inst.Arg(0)}; | ||
| 433 | const IR::Value rhs_value{inst.Arg(1)}; | ||
| 434 | IR::Inst* const lhs_op{lhs_value.InstRecursive()}; | ||
| 435 | IR::Inst* const rhs_op{rhs_value.InstRecursive()}; | ||
| 436 | if (lhs_op->GetOpcode() == IR::Opcode::YDirection) { | ||
| 437 | if (rhs_op->GetOpcode() != IR::Opcode::DPdyFine) { | ||
| 438 | return false; | ||
| 439 | } | ||
| 440 | inst.ReplaceUsesWith(rhs_value); | ||
| 441 | return true; | ||
| 442 | } | ||
| 443 | if (rhs_op->GetOpcode() != IR::Opcode::YDirection) { | ||
| 444 | return false; | ||
| 445 | } | ||
| 446 | if (lhs_op->GetOpcode() != IR::Opcode::DPdyFine) { | ||
| 447 | return false; | ||
| 448 | } | ||
| 449 | inst.ReplaceUsesWith(lhs_value); | ||
| 450 | return true; | ||
| 451 | } | ||
| 452 | |||
| 413 | void FoldFPMul32(IR::Inst& inst) { | 453 | void FoldFPMul32(IR::Inst& inst) { |
| 454 | if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a * b; })) { | ||
| 455 | return; | ||
| 456 | } | ||
| 414 | const auto control{inst.Flags<IR::FpControl>()}; | 457 | const auto control{inst.Flags<IR::FpControl>()}; |
| 415 | if (control.no_contraction) { | 458 | if (control.no_contraction) { |
| 416 | return; | 459 | return; |
| @@ -421,6 +464,9 @@ void FoldFPMul32(IR::Inst& inst) { | |||
| 421 | if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) { | 464 | if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) { |
| 422 | return; | 465 | return; |
| 423 | } | 466 | } |
| 467 | if (FoldDerivateYFromCorrection(inst)) { | ||
| 468 | return; | ||
| 469 | } | ||
| 424 | IR::Inst* const lhs_op{lhs_value.InstRecursive()}; | 470 | IR::Inst* const lhs_op{lhs_value.InstRecursive()}; |
| 425 | IR::Inst* const rhs_op{rhs_value.InstRecursive()}; | 471 | IR::Inst* const rhs_op{rhs_value.InstRecursive()}; |
| 426 | if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 || | 472 | if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 || |
| @@ -622,7 +668,12 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { | |||
| 622 | } | 668 | } |
| 623 | const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)}; | 669 | const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)}; |
| 624 | if (value_2 != value_3) { | 670 | if (value_2 != value_3) { |
| 625 | return; | 671 | if (!value_2.IsImmediate() || !value_3.IsImmediate()) { |
| 672 | return; | ||
| 673 | } | ||
| 674 | if (Common::BitCast<u32>(value_2.F32()) != value_3.U32()) { | ||
| 675 | return; | ||
| 676 | } | ||
| 626 | } | 677 | } |
| 627 | const IR::Value index{inst2->Arg(1)}; | 678 | const IR::Value index{inst2->Arg(1)}; |
| 628 | const IR::Value clamp{inst2->Arg(2)}; | 679 | const IR::Value clamp{inst2->Arg(2)}; |
| @@ -648,6 +699,169 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { | |||
| 648 | } | 699 | } |
| 649 | } | 700 | } |
| 650 | 701 | ||
| 702 | bool FindGradient3DDerivates(std::array<IR::Value, 3>& results, IR::Value coord) { | ||
| 703 | if (coord.IsImmediate()) { | ||
| 704 | return false; | ||
| 705 | } | ||
| 706 | const auto check_through_shuffle = [](IR::Value input, IR::Value& result) { | ||
| 707 | const IR::Value value_1{GetThroughCast(input.Resolve(), IR::Opcode::BitCastF32U32)}; | ||
| 708 | IR::Inst* const inst2{value_1.InstRecursive()}; | ||
| 709 | if (inst2->GetOpcode() != IR::Opcode::ShuffleIndex) { | ||
| 710 | return false; | ||
| 711 | } | ||
| 712 | const IR::Value index{inst2->Arg(1).Resolve()}; | ||
| 713 | const IR::Value clamp{inst2->Arg(2).Resolve()}; | ||
| 714 | const IR::Value segmentation_mask{inst2->Arg(3).Resolve()}; | ||
| 715 | if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) { | ||
| 716 | return false; | ||
| 717 | } | ||
| 718 | if (index.U32() != 3 && clamp.U32() != 3) { | ||
| 719 | return false; | ||
| 720 | } | ||
| 721 | result = GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32); | ||
| 722 | return true; | ||
| 723 | }; | ||
| 724 | IR::Inst* const inst = coord.InstRecursive(); | ||
| 725 | if (inst->GetOpcode() != IR::Opcode::FSwizzleAdd) { | ||
| 726 | return false; | ||
| 727 | } | ||
| 728 | std::array<IR::Value, 3> temporary_values; | ||
| 729 | IR::Value value_1 = inst->Arg(0).Resolve(); | ||
| 730 | IR::Value value_2 = inst->Arg(1).Resolve(); | ||
| 731 | IR::Value value_3 = inst->Arg(2).Resolve(); | ||
| 732 | std::array<u32, 4> swizzles_mask_a{}; | ||
| 733 | std::array<u32, 4> swizzles_mask_b{}; | ||
| 734 | const auto resolve_mask = [](std::array<u32, 4>& mask_results, IR::Value mask) { | ||
| 735 | u32 value = mask.U32(); | ||
| 736 | for (size_t i = 0; i < 4; i++) { | ||
| 737 | mask_results[i] = (value >> (i * 2)) & 0x3; | ||
| 738 | } | ||
| 739 | }; | ||
| 740 | resolve_mask(swizzles_mask_a, value_3); | ||
| 741 | size_t coordinate_index = 0; | ||
| 742 | const auto resolve_pending = [&](IR::Value resolve_v) { | ||
| 743 | IR::Inst* const inst_r = resolve_v.InstRecursive(); | ||
| 744 | if (inst_r->GetOpcode() != IR::Opcode::FSwizzleAdd) { | ||
| 745 | return false; | ||
| 746 | } | ||
| 747 | if (!check_through_shuffle(inst_r->Arg(0).Resolve(), temporary_values[1])) { | ||
| 748 | return false; | ||
| 749 | } | ||
| 750 | if (!check_through_shuffle(inst_r->Arg(1).Resolve(), temporary_values[2])) { | ||
| 751 | return false; | ||
| 752 | } | ||
| 753 | resolve_mask(swizzles_mask_b, inst_r->Arg(2).Resolve()); | ||
| 754 | return true; | ||
| 755 | }; | ||
| 756 | if (value_1.IsImmediate() || value_2.IsImmediate()) { | ||
| 757 | return false; | ||
| 758 | } | ||
| 759 | bool should_continue = false; | ||
| 760 | if (resolve_pending(value_1)) { | ||
| 761 | should_continue = check_through_shuffle(value_2, temporary_values[0]); | ||
| 762 | coordinate_index = 0; | ||
| 763 | } | ||
| 764 | if (resolve_pending(value_2)) { | ||
| 765 | should_continue = check_through_shuffle(value_1, temporary_values[0]); | ||
| 766 | coordinate_index = 2; | ||
| 767 | } | ||
| 768 | if (!should_continue) { | ||
| 769 | return false; | ||
| 770 | } | ||
| 771 | // figure which is which | ||
| 772 | size_t zero_mask_a = 0; | ||
| 773 | size_t zero_mask_b = 0; | ||
| 774 | for (size_t i = 0; i < 4; i++) { | ||
| 775 | if (swizzles_mask_a[i] == 2 || swizzles_mask_b[i] == 2) { | ||
| 776 | // last operand can be inversed, we cannot determine a result. | ||
| 777 | return false; | ||
| 778 | } | ||
| 779 | zero_mask_a |= static_cast<size_t>(swizzles_mask_a[i] == 3 ? 1 : 0) << i; | ||
| 780 | zero_mask_b |= static_cast<size_t>(swizzles_mask_b[i] == 3 ? 1 : 0) << i; | ||
| 781 | } | ||
| 782 | static constexpr size_t ddx_pattern = 0b1010; | ||
| 783 | static constexpr size_t ddx_pattern_inv = ~ddx_pattern & 0b00001111; | ||
| 784 | if (std::popcount(zero_mask_a) != 2) { | ||
| 785 | return false; | ||
| 786 | } | ||
| 787 | if (std::popcount(zero_mask_b) != 2) { | ||
| 788 | return false; | ||
| 789 | } | ||
| 790 | if (zero_mask_a == zero_mask_b) { | ||
| 791 | return false; | ||
| 792 | } | ||
| 793 | results[0] = temporary_values[coordinate_index]; | ||
| 794 | |||
| 795 | if (coordinate_index == 0) { | ||
| 796 | if (zero_mask_b == ddx_pattern || zero_mask_b == ddx_pattern_inv) { | ||
| 797 | results[1] = temporary_values[1]; | ||
| 798 | results[2] = temporary_values[2]; | ||
| 799 | return true; | ||
| 800 | } | ||
| 801 | results[2] = temporary_values[1]; | ||
| 802 | results[1] = temporary_values[2]; | ||
| 803 | } else { | ||
| 804 | const auto assign_result = [&results](IR::Value temporary_value, size_t mask) { | ||
| 805 | if (mask == ddx_pattern || mask == ddx_pattern_inv) { | ||
| 806 | results[1] = temporary_value; | ||
| 807 | return; | ||
| 808 | } | ||
| 809 | results[2] = temporary_value; | ||
| 810 | }; | ||
| 811 | assign_result(temporary_values[1], zero_mask_b); | ||
| 812 | assign_result(temporary_values[0], zero_mask_a); | ||
| 813 | } | ||
| 814 | |||
| 815 | return true; | ||
| 816 | } | ||
| 817 | |||
| 818 | void FoldImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) { | ||
| 819 | IR::TextureInstInfo info = inst.Flags<IR::TextureInstInfo>(); | ||
| 820 | auto orig_opcode = inst.GetOpcode(); | ||
| 821 | if (info.ndv_is_active == 0) { | ||
| 822 | return; | ||
| 823 | } | ||
| 824 | if (info.type != TextureType::Color3D) { | ||
| 825 | return; | ||
| 826 | } | ||
| 827 | const IR::Value handle{inst.Arg(0)}; | ||
| 828 | const IR::Value coords{inst.Arg(1)}; | ||
| 829 | const IR::Value bias_lc{inst.Arg(2)}; | ||
| 830 | const IR::Value offset{inst.Arg(3)}; | ||
| 831 | if (!offset.IsImmediate()) { | ||
| 832 | return; | ||
| 833 | } | ||
| 834 | IR::Inst* const inst2 = coords.InstRecursive(); | ||
| 835 | std::array<std::array<IR::Value, 3>, 3> results_matrix; | ||
| 836 | for (size_t i = 0; i < 3; i++) { | ||
| 837 | if (!FindGradient3DDerivates(results_matrix[i], inst2->Arg(i).Resolve())) { | ||
| 838 | return; | ||
| 839 | } | ||
| 840 | } | ||
| 841 | IR::F32 lod_clamp{}; | ||
| 842 | if (info.has_lod_clamp != 0) { | ||
| 843 | if (!bias_lc.IsImmediate()) { | ||
| 844 | lod_clamp = IR::F32{bias_lc.InstRecursive()->Arg(1).Resolve()}; | ||
| 845 | } else { | ||
| 846 | lod_clamp = IR::F32{bias_lc}; | ||
| 847 | } | ||
| 848 | } | ||
| 849 | IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; | ||
| 850 | IR::Value new_coords = | ||
| 851 | ir.CompositeConstruct(results_matrix[0][0], results_matrix[1][0], results_matrix[2][0]); | ||
| 852 | IR::Value derivatives_1 = ir.CompositeConstruct(results_matrix[0][1], results_matrix[0][2], | ||
| 853 | results_matrix[1][1], results_matrix[1][2]); | ||
| 854 | IR::Value derivatives_2 = ir.CompositeConstruct(results_matrix[2][1], results_matrix[2][2]); | ||
| 855 | info.num_derivates.Assign(3); | ||
| 856 | IR::Value new_gradient_instruction = | ||
| 857 | ir.ImageGradient(handle, new_coords, derivatives_1, derivatives_2, lod_clamp, info); | ||
| 858 | IR::Inst* const new_inst = new_gradient_instruction.InstRecursive(); | ||
| 859 | if (orig_opcode == IR::Opcode::ImageSampleImplicitLod) { | ||
| 860 | new_inst->ReplaceOpcode(IR::Opcode::ImageGradient); | ||
| 861 | } | ||
| 862 | inst.ReplaceUsesWith(new_gradient_instruction); | ||
| 863 | } | ||
| 864 | |||
| 651 | void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) { | 865 | void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) { |
| 652 | const IR::Value bank{inst.Arg(0)}; | 866 | const IR::Value bank{inst.Arg(0)}; |
| 653 | const IR::Value offset{inst.Arg(1)}; | 867 | const IR::Value offset{inst.Arg(1)}; |
| @@ -743,6 +957,12 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) { | |||
| 743 | case IR::Opcode::SelectF32: | 957 | case IR::Opcode::SelectF32: |
| 744 | case IR::Opcode::SelectF64: | 958 | case IR::Opcode::SelectF64: |
| 745 | return FoldSelect(inst); | 959 | return FoldSelect(inst); |
| 960 | case IR::Opcode::FPNeg32: | ||
| 961 | FoldWhenAllImmediates(inst, [](f32 a) { return -a; }); | ||
| 962 | return; | ||
| 963 | case IR::Opcode::FPAdd32: | ||
| 964 | FoldFPAdd32(inst); | ||
| 965 | return; | ||
| 746 | case IR::Opcode::FPMul32: | 966 | case IR::Opcode::FPMul32: |
| 747 | return FoldFPMul32(inst); | 967 | return FoldFPMul32(inst); |
| 748 | case IR::Opcode::LogicalAnd: | 968 | case IR::Opcode::LogicalAnd: |
| @@ -858,6 +1078,11 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) { | |||
| 858 | FoldDriverConstBuffer(env, block, inst, 1); | 1078 | FoldDriverConstBuffer(env, block, inst, 1); |
| 859 | } | 1079 | } |
| 860 | break; | 1080 | break; |
| 1081 | case IR::Opcode::BindlessImageSampleImplicitLod: | ||
| 1082 | case IR::Opcode::BoundImageSampleImplicitLod: | ||
| 1083 | case IR::Opcode::ImageSampleImplicitLod: | ||
| 1084 | FoldImageSampleImplicitLod(block, inst); | ||
| 1085 | break; | ||
| 861 | default: | 1086 | default: |
| 862 | break; | 1087 | break; |
| 863 | } | 1088 | } |
diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp index e85f9977b..b6e3bc875 100644 --- a/src/tests/common/ring_buffer.cpp +++ b/src/tests/common/ring_buffer.cpp | |||
| @@ -55,7 +55,7 @@ TEST_CASE("RingBuffer: Basic Tests", "[common]") { | |||
| 55 | // Pushing more values than space available should partially succeed. | 55 | // Pushing more values than space available should partially succeed. |
| 56 | { | 56 | { |
| 57 | std::vector<char> to_push(6); | 57 | std::vector<char> to_push(6); |
| 58 | std::iota(to_push.begin(), to_push.end(), 88); | 58 | std::iota(to_push.begin(), to_push.end(), static_cast<char>(88)); |
| 59 | const std::size_t count = buf.Push(to_push); | 59 | const std::size_t count = buf.Push(to_push); |
| 60 | REQUIRE(count == 3U); | 60 | REQUIRE(count == 3U); |
| 61 | } | 61 | } |
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index f0f450edb..8be7bd594 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -289,8 +289,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad | |||
| 289 | MarkWrittenBuffer(buffer_id, *cpu_addr, size); | 289 | MarkWrittenBuffer(buffer_id, *cpu_addr, size); |
| 290 | break; | 290 | break; |
| 291 | case ObtainBufferOperation::DiscardWrite: { | 291 | case ObtainBufferOperation::DiscardWrite: { |
| 292 | IntervalType interval{*cpu_addr, size}; | 292 | VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64); |
| 293 | VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64); | ||
| 294 | IntervalType interval{cpu_addr_start, cpu_addr_end}; | ||
| 293 | ClearDownload(interval); | 295 | ClearDownload(interval); |
| 296 | common_ranges.subtract(interval); | ||
| 294 | break; | 297 | break; |
| 295 | } | 298 | } |
| 296 | default: | 299 | default: |
| @@ -1159,6 +1162,11 @@ void BufferCache<P>::UpdateDrawIndirect() { | |||
| 1159 | .size = static_cast<u32>(size), | 1162 | .size = static_cast<u32>(size), |
| 1160 | .buffer_id = FindBuffer(*cpu_addr, static_cast<u32>(size)), | 1163 | .buffer_id = FindBuffer(*cpu_addr, static_cast<u32>(size)), |
| 1161 | }; | 1164 | }; |
| 1165 | VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64); | ||
| 1166 | VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64); | ||
| 1167 | IntervalType interval{cpu_addr_start, cpu_addr_end}; | ||
| 1168 | ClearDownload(interval); | ||
| 1169 | common_ranges.subtract(interval); | ||
| 1162 | }; | 1170 | }; |
| 1163 | if (current_draw_indirect->include_count) { | 1171 | if (current_draw_indirect->include_count) { |
| 1164 | update(current_draw_indirect->count_start_address, sizeof(u32), | 1172 | update(current_draw_indirect->count_start_address, sizeof(u32), |
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 9f1b340a9..58ce0d8c2 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | namespace Tegra { | 14 | namespace Tegra { |
| 15 | 15 | ||
| 16 | constexpr u32 MacroRegistersStart = 0xE00; | 16 | constexpr u32 MacroRegistersStart = 0xE00; |
| 17 | constexpr u32 ComputeInline = 0x6D; | ||
| 17 | 18 | ||
| 18 | DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, | 19 | DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, |
| 19 | Control::ChannelState& channel_state_) | 20 | Control::ChannelState& channel_state_) |
| @@ -83,12 +84,35 @@ bool DmaPusher::Step() { | |||
| 83 | dma_state.dma_get, command_list_header.size * sizeof(u32)); | 84 | dma_state.dma_get, command_list_header.size * sizeof(u32)); |
| 84 | } | 85 | } |
| 85 | } | 86 | } |
| 86 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, | 87 | const auto safe_process = [&] { |
| 87 | Core::Memory::GuestMemoryFlags::UnsafeRead> | 88 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, |
| 88 | headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); | 89 | Core::Memory::GuestMemoryFlags::SafeRead> |
| 89 | ProcessCommands(headers); | 90 | headers(memory_manager, dma_state.dma_get, command_list_header.size, |
| 91 | &command_headers); | ||
| 92 | ProcessCommands(headers); | ||
| 93 | }; | ||
| 94 | const auto unsafe_process = [&] { | ||
| 95 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, | ||
| 96 | Core::Memory::GuestMemoryFlags::UnsafeRead> | ||
| 97 | headers(memory_manager, dma_state.dma_get, command_list_header.size, | ||
| 98 | &command_headers); | ||
| 99 | ProcessCommands(headers); | ||
| 100 | }; | ||
| 101 | if (Settings::IsGPULevelHigh()) { | ||
| 102 | if (dma_state.method >= MacroRegistersStart) { | ||
| 103 | unsafe_process(); | ||
| 104 | return true; | ||
| 105 | } | ||
| 106 | if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute && | ||
| 107 | dma_state.method == ComputeInline) { | ||
| 108 | unsafe_process(); | ||
| 109 | return true; | ||
| 110 | } | ||
| 111 | safe_process(); | ||
| 112 | return true; | ||
| 113 | } | ||
| 114 | unsafe_process(); | ||
| 90 | } | 115 | } |
| 91 | |||
| 92 | return true; | 116 | return true; |
| 93 | } | 117 | } |
| 94 | 118 | ||
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 8a2784cdc..c9fab2d90 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h | |||
| @@ -130,8 +130,10 @@ public: | |||
| 130 | 130 | ||
| 131 | void DispatchCalls(); | 131 | void DispatchCalls(); |
| 132 | 132 | ||
| 133 | void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) { | 133 | void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id, |
| 134 | Engines::EngineTypes engine_type) { | ||
| 134 | subchannels[subchannel_id] = engine; | 135 | subchannels[subchannel_id] = engine; |
| 136 | subchannel_type[subchannel_id] = engine_type; | ||
| 135 | } | 137 | } |
| 136 | 138 | ||
| 137 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); | 139 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); |
| @@ -170,6 +172,7 @@ private: | |||
| 170 | const bool ib_enable{true}; ///< IB mode enabled | 172 | const bool ib_enable{true}; ///< IB mode enabled |
| 171 | 173 | ||
| 172 | std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; | 174 | std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; |
| 175 | std::array<Engines::EngineTypes, max_subchannels> subchannel_type; | ||
| 173 | 176 | ||
| 174 | GPU& gpu; | 177 | GPU& gpu; |
| 175 | Core::System& system; | 178 | Core::System& system; |
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h index 392322358..54631ee6c 100644 --- a/src/video_core/engines/engine_interface.h +++ b/src/video_core/engines/engine_interface.h | |||
| @@ -11,6 +11,14 @@ | |||
| 11 | 11 | ||
| 12 | namespace Tegra::Engines { | 12 | namespace Tegra::Engines { |
| 13 | 13 | ||
| 14 | enum class EngineTypes : u32 { | ||
| 15 | KeplerCompute, | ||
| 16 | Maxwell3D, | ||
| 17 | Fermi2D, | ||
| 18 | MaxwellDMA, | ||
| 19 | KeplerMemory, | ||
| 20 | }; | ||
| 21 | |||
| 14 | class EngineInterface { | 22 | class EngineInterface { |
| 15 | public: | 23 | public: |
| 16 | virtual ~EngineInterface() = default; | 24 | virtual ~EngineInterface() = default; |
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index 7242d2529..21bf8aeb4 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h | |||
| @@ -69,6 +69,14 @@ public: | |||
| 69 | /// Binds a rasterizer to this engine. | 69 | /// Binds a rasterizer to this engine. |
| 70 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); | 70 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); |
| 71 | 71 | ||
| 72 | GPUVAddr ExecTargetAddress() const { | ||
| 73 | return regs.dest.Address(); | ||
| 74 | } | ||
| 75 | |||
| 76 | u32 GetUploadSize() const { | ||
| 77 | return copy_size; | ||
| 78 | } | ||
| 79 | |||
| 72 | private: | 80 | private: |
| 73 | void ProcessData(std::span<const u8> read_buffer); | 81 | void ProcessData(std::span<const u8> read_buffer); |
| 74 | 82 | ||
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index a38d9528a..cd61ab222 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp | |||
| @@ -43,16 +43,33 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal | |||
| 43 | 43 | ||
| 44 | switch (method) { | 44 | switch (method) { |
| 45 | case KEPLER_COMPUTE_REG_INDEX(exec_upload): { | 45 | case KEPLER_COMPUTE_REG_INDEX(exec_upload): { |
| 46 | UploadInfo info{.upload_address = upload_address, | ||
| 47 | .exec_address = upload_state.ExecTargetAddress(), | ||
| 48 | .copy_size = upload_state.GetUploadSize()}; | ||
| 49 | uploads.push_back(info); | ||
| 46 | upload_state.ProcessExec(regs.exec_upload.linear != 0); | 50 | upload_state.ProcessExec(regs.exec_upload.linear != 0); |
| 47 | break; | 51 | break; |
| 48 | } | 52 | } |
| 49 | case KEPLER_COMPUTE_REG_INDEX(data_upload): { | 53 | case KEPLER_COMPUTE_REG_INDEX(data_upload): { |
| 54 | upload_address = current_dma_segment; | ||
| 50 | upload_state.ProcessData(method_argument, is_last_call); | 55 | upload_state.ProcessData(method_argument, is_last_call); |
| 51 | break; | 56 | break; |
| 52 | } | 57 | } |
| 53 | case KEPLER_COMPUTE_REG_INDEX(launch): | 58 | case KEPLER_COMPUTE_REG_INDEX(launch): { |
| 59 | const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); | ||
| 60 | |||
| 61 | for (auto& data : uploads) { | ||
| 62 | const GPUVAddr offset = data.exec_address - launch_desc_loc; | ||
| 63 | if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) && | ||
| 64 | memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) { | ||
| 65 | indirect_compute = {data.upload_address}; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | uploads.clear(); | ||
| 54 | ProcessLaunch(); | 69 | ProcessLaunch(); |
| 70 | indirect_compute = std::nullopt; | ||
| 55 | break; | 71 | break; |
| 72 | } | ||
| 56 | default: | 73 | default: |
| 57 | break; | 74 | break; |
| 58 | } | 75 | } |
| @@ -62,6 +79,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun | |||
| 62 | u32 methods_pending) { | 79 | u32 methods_pending) { |
| 63 | switch (method) { | 80 | switch (method) { |
| 64 | case KEPLER_COMPUTE_REG_INDEX(data_upload): | 81 | case KEPLER_COMPUTE_REG_INDEX(data_upload): |
| 82 | upload_address = current_dma_segment; | ||
| 65 | upload_state.ProcessData(base_start, amount); | 83 | upload_state.ProcessData(base_start, amount); |
| 66 | return; | 84 | return; |
| 67 | default: | 85 | default: |
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 2092e685f..735e05fb4 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include <array> | 6 | #include <array> |
| 7 | #include <cstddef> | 7 | #include <cstddef> |
| 8 | #include <optional> | ||
| 8 | #include <vector> | 9 | #include <vector> |
| 9 | #include "common/bit_field.h" | 10 | #include "common/bit_field.h" |
| 10 | #include "common/common_funcs.h" | 11 | #include "common/common_funcs.h" |
| @@ -36,6 +37,9 @@ namespace Tegra::Engines { | |||
| 36 | #define KEPLER_COMPUTE_REG_INDEX(field_name) \ | 37 | #define KEPLER_COMPUTE_REG_INDEX(field_name) \ |
| 37 | (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) | 38 | (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) |
| 38 | 39 | ||
| 40 | #define LAUNCH_REG_INDEX(field_name) \ | ||
| 41 | (offsetof(Tegra::Engines::KeplerCompute::LaunchParams, field_name) / sizeof(u32)) | ||
| 42 | |||
| 39 | class KeplerCompute final : public EngineInterface { | 43 | class KeplerCompute final : public EngineInterface { |
| 40 | public: | 44 | public: |
| 41 | explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); | 45 | explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); |
| @@ -201,6 +205,10 @@ public: | |||
| 201 | void CallMultiMethod(u32 method, const u32* base_start, u32 amount, | 205 | void CallMultiMethod(u32 method, const u32* base_start, u32 amount, |
| 202 | u32 methods_pending) override; | 206 | u32 methods_pending) override; |
| 203 | 207 | ||
| 208 | std::optional<GPUVAddr> GetIndirectComputeAddress() const { | ||
| 209 | return indirect_compute; | ||
| 210 | } | ||
| 211 | |||
| 204 | private: | 212 | private: |
| 205 | void ProcessLaunch(); | 213 | void ProcessLaunch(); |
| 206 | 214 | ||
| @@ -216,6 +224,15 @@ private: | |||
| 216 | MemoryManager& memory_manager; | 224 | MemoryManager& memory_manager; |
| 217 | VideoCore::RasterizerInterface* rasterizer = nullptr; | 225 | VideoCore::RasterizerInterface* rasterizer = nullptr; |
| 218 | Upload::State upload_state; | 226 | Upload::State upload_state; |
| 227 | GPUVAddr upload_address; | ||
| 228 | |||
| 229 | struct UploadInfo { | ||
| 230 | GPUVAddr upload_address; | ||
| 231 | GPUVAddr exec_address; | ||
| 232 | u32 copy_size; | ||
| 233 | }; | ||
| 234 | std::vector<UploadInfo> uploads; | ||
| 235 | std::optional<GPUVAddr> indirect_compute{}; | ||
| 219 | }; | 236 | }; |
| 220 | 237 | ||
| 221 | #define ASSERT_REG_POSITION(field_name, position) \ | 238 | #define ASSERT_REG_POSITION(field_name, position) \ |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index c3696096d..06e349e43 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -257,6 +257,7 @@ u32 Maxwell3D::GetMaxCurrentVertices() { | |||
| 257 | const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); | 257 | const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); |
| 258 | num_vertices = std::max( | 258 | num_vertices = std::max( |
| 259 | num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); | 259 | num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); |
| 260 | break; | ||
| 260 | } | 261 | } |
| 261 | return num_vertices; | 262 | return num_vertices; |
| 262 | } | 263 | } |
| @@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() { | |||
| 269 | std::numeric_limits<u32>::max()}; | 270 | std::numeric_limits<u32>::max()}; |
| 270 | const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); | 271 | const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); |
| 271 | const size_t log2_byte_size = Common::Log2Ceil64(byte_size); | 272 | const size_t log2_byte_size = Common::Log2Ceil64(byte_size); |
| 273 | const size_t cap{GetMaxCurrentVertices() * 3 * byte_size}; | ||
| 274 | const size_t lower_cap = | ||
| 275 | std::min<size_t>(static_cast<size_t>(end_address - start_address), cap); | ||
| 272 | return std::min<size_t>( | 276 | return std::min<size_t>( |
| 273 | memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / | 277 | memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / |
| 274 | byte_size, | 278 | byte_size, |
| 275 | static_cast<size_t>(end_address - start_address)); | 279 | lower_cap); |
| 276 | } | 280 | } |
| 277 | 281 | ||
| 278 | u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { | 282 | u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { |
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index cd8e24b0b..da8eab7ee 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "common/assert.h" | 5 | #include "common/assert.h" |
| 6 | #include "common/logging/log.h" | 6 | #include "common/logging/log.h" |
| 7 | #include "common/microprofile.h" | 7 | #include "common/microprofile.h" |
| 8 | #include "common/polyfill_ranges.h" | ||
| 8 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 9 | #include "core/core.h" | 10 | #include "core/core.h" |
| 10 | #include "core/memory.h" | 11 | #include "core/memory.h" |
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 7718a09b3..6de2543b7 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp | |||
| @@ -34,19 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) { | |||
| 34 | bound_engines[method_call.subchannel] = engine_id; | 34 | bound_engines[method_call.subchannel] = engine_id; |
| 35 | switch (engine_id) { | 35 | switch (engine_id) { |
| 36 | case EngineID::FERMI_TWOD_A: | 36 | case EngineID::FERMI_TWOD_A: |
| 37 | dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel); | 37 | dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel, |
| 38 | EngineTypes::Fermi2D); | ||
| 38 | break; | 39 | break; |
| 39 | case EngineID::MAXWELL_B: | 40 | case EngineID::MAXWELL_B: |
| 40 | dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel); | 41 | dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel, |
| 42 | EngineTypes::Maxwell3D); | ||
| 41 | break; | 43 | break; |
| 42 | case EngineID::KEPLER_COMPUTE_B: | 44 | case EngineID::KEPLER_COMPUTE_B: |
| 43 | dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel); | 45 | dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel, |
| 46 | EngineTypes::KeplerCompute); | ||
| 44 | break; | 47 | break; |
| 45 | case EngineID::MAXWELL_DMA_COPY_A: | 48 | case EngineID::MAXWELL_DMA_COPY_A: |
| 46 | dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel); | 49 | dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel, |
| 50 | EngineTypes::MaxwellDMA); | ||
| 47 | break; | 51 | break; |
| 48 | case EngineID::KEPLER_INLINE_TO_MEMORY_B: | 52 | case EngineID::KEPLER_INLINE_TO_MEMORY_B: |
| 49 | dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel); | 53 | dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel, |
| 54 | EngineTypes::KeplerMemory); | ||
| 50 | break; | 55 | break; |
| 51 | default: | 56 | default: |
| 52 | UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); | 57 | UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); |
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp index 220cce28a..8d7da50fc 100644 --- a/src/video_core/host1x/codecs/codec.cpp +++ b/src/video_core/host1x/codecs/codec.cpp | |||
| @@ -319,6 +319,7 @@ void Codec::Decode() { | |||
| 319 | LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); | 319 | LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); |
| 320 | return; | 320 | return; |
| 321 | } | 321 | } |
| 322 | bool is_interlaced = initial_frame->interlaced_frame != 0; | ||
| 322 | if (av_codec_ctx->hw_device_ctx) { | 323 | if (av_codec_ctx->hw_device_ctx) { |
| 323 | final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; | 324 | final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; |
| 324 | ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); | 325 | ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); |
| @@ -334,7 +335,7 @@ void Codec::Decode() { | |||
| 334 | UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); | 335 | UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); |
| 335 | return; | 336 | return; |
| 336 | } | 337 | } |
| 337 | if (!final_frame->interlaced_frame) { | 338 | if (!is_interlaced) { |
| 338 | av_frames.push(std::move(final_frame)); | 339 | av_frames.push(std::move(final_frame)); |
| 339 | } else { | 340 | } else { |
| 340 | if (!filters_initialized) { | 341 | if (!filters_initialized) { |
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index e61d9af80..c4d459077 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt | |||
| @@ -50,6 +50,7 @@ set(SHADER_FILES | |||
| 50 | vulkan_blit_depth_stencil.frag | 50 | vulkan_blit_depth_stencil.frag |
| 51 | vulkan_color_clear.frag | 51 | vulkan_color_clear.frag |
| 52 | vulkan_color_clear.vert | 52 | vulkan_color_clear.vert |
| 53 | vulkan_depthstencil_clear.frag | ||
| 53 | vulkan_fidelityfx_fsr_easu_fp16.comp | 54 | vulkan_fidelityfx_fsr_easu_fp16.comp |
| 54 | vulkan_fidelityfx_fsr_easu_fp32.comp | 55 | vulkan_fidelityfx_fsr_easu_fp32.comp |
| 55 | vulkan_fidelityfx_fsr_rcas_fp16.comp | 56 | vulkan_fidelityfx_fsr_rcas_fp16.comp |
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index bf2693559..5ff17cd0c 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp | |||
| @@ -33,26 +33,14 @@ UNIFORM(6) uint block_height_mask; | |||
| 33 | END_PUSH_CONSTANTS | 33 | END_PUSH_CONSTANTS |
| 34 | 34 | ||
| 35 | struct EncodingData { | 35 | struct EncodingData { |
| 36 | uint encoding; | 36 | uint data; |
| 37 | uint num_bits; | ||
| 38 | uint bit_value; | ||
| 39 | uint quint_trit_value; | ||
| 40 | }; | 37 | }; |
| 41 | 38 | ||
| 42 | struct TexelWeightParams { | 39 | layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBufferU32 { |
| 43 | uvec2 size; | ||
| 44 | uint max_weight; | ||
| 45 | bool dual_plane; | ||
| 46 | bool error_state; | ||
| 47 | bool void_extent_ldr; | ||
| 48 | bool void_extent_hdr; | ||
| 49 | }; | ||
| 50 | |||
| 51 | layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 { | ||
| 52 | uvec4 astc_data[]; | 40 | uvec4 astc_data[]; |
| 53 | }; | 41 | }; |
| 54 | 42 | ||
| 55 | layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image; | 43 | layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; |
| 56 | 44 | ||
| 57 | const uint GOB_SIZE_X_SHIFT = 6; | 45 | const uint GOB_SIZE_X_SHIFT = 6; |
| 58 | const uint GOB_SIZE_Y_SHIFT = 3; | 46 | const uint GOB_SIZE_Y_SHIFT = 3; |
| @@ -60,64 +48,21 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT; | |||
| 60 | 48 | ||
| 61 | const uint BYTES_PER_BLOCK_LOG2 = 4; | 49 | const uint BYTES_PER_BLOCK_LOG2 = 4; |
| 62 | 50 | ||
| 63 | const int JUST_BITS = 0; | 51 | const uint JUST_BITS = 0u; |
| 64 | const int QUINT = 1; | 52 | const uint QUINT = 1u; |
| 65 | const int TRIT = 2; | 53 | const uint TRIT = 2u; |
| 66 | 54 | ||
| 67 | // ASTC Encodings data, sorted in ascending order based on their BitLength value | 55 | // ASTC Encodings data, sorted in ascending order based on their BitLength value |
| 68 | // (see GetBitLength() function) | 56 | // (see GetBitLength() function) |
| 69 | EncodingData encoding_values[22] = EncodingData[]( | 57 | const uint encoding_values[22] = uint[]( |
| 70 | EncodingData(JUST_BITS, 0, 0, 0), EncodingData(JUST_BITS, 1, 0, 0), EncodingData(TRIT, 0, 0, 0), | 58 | (JUST_BITS), (JUST_BITS | (1u << 8u)), (TRIT), (JUST_BITS | (2u << 8u)), |
| 71 | EncodingData(JUST_BITS, 2, 0, 0), EncodingData(QUINT, 0, 0, 0), EncodingData(TRIT, 1, 0, 0), | 59 | (QUINT), (TRIT | (1u << 8u)), (JUST_BITS | (3u << 8u)), (QUINT | (1u << 8u)), |
| 72 | EncodingData(JUST_BITS, 3, 0, 0), EncodingData(QUINT, 1, 0, 0), EncodingData(TRIT, 2, 0, 0), | 60 | (TRIT | (2u << 8u)), (JUST_BITS | (4u << 8u)), (QUINT | (2u << 8u)), (TRIT | (3u << 8u)), |
| 73 | EncodingData(JUST_BITS, 4, 0, 0), EncodingData(QUINT, 2, 0, 0), EncodingData(TRIT, 3, 0, 0), | 61 | (JUST_BITS | (5u << 8u)), (QUINT | (3u << 8u)), (TRIT | (4u << 8u)), (JUST_BITS | (6u << 8u)), |
| 74 | EncodingData(JUST_BITS, 5, 0, 0), EncodingData(QUINT, 3, 0, 0), EncodingData(TRIT, 4, 0, 0), | 62 | (QUINT | (4u << 8u)), (TRIT | (5u << 8u)), (JUST_BITS | (7u << 8u)), (QUINT | (5u << 8u)), |
| 75 | EncodingData(JUST_BITS, 6, 0, 0), EncodingData(QUINT, 4, 0, 0), EncodingData(TRIT, 5, 0, 0), | 63 | (TRIT | (6u << 8u)), (JUST_BITS | (8u << 8u))); |
| 76 | EncodingData(JUST_BITS, 7, 0, 0), EncodingData(QUINT, 5, 0, 0), EncodingData(TRIT, 6, 0, 0), | ||
| 77 | EncodingData(JUST_BITS, 8, 0, 0) | ||
| 78 | ); | ||
| 79 | |||
| 80 | // The following constants are expanded variants of the Replicate() | ||
| 81 | // function calls corresponding to the following arguments: | ||
| 82 | // value: index into the generated table | ||
| 83 | // num_bits: the after "REPLICATE" in the table name. i.e. 4 is num_bits in REPLICATE_4. | ||
| 84 | // to_bit: the integer after "TO_" | ||
| 85 | const uint REPLICATE_BIT_TO_7_TABLE[2] = uint[](0, 127); | ||
| 86 | const uint REPLICATE_1_BIT_TO_9_TABLE[2] = uint[](0, 511); | ||
| 87 | |||
| 88 | const uint REPLICATE_1_BIT_TO_8_TABLE[2] = uint[](0, 255); | ||
| 89 | const uint REPLICATE_2_BIT_TO_8_TABLE[4] = uint[](0, 85, 170, 255); | ||
| 90 | const uint REPLICATE_3_BIT_TO_8_TABLE[8] = uint[](0, 36, 73, 109, 146, 182, 219, 255); | ||
| 91 | const uint REPLICATE_4_BIT_TO_8_TABLE[16] = | ||
| 92 | uint[](0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255); | ||
| 93 | const uint REPLICATE_5_BIT_TO_8_TABLE[32] = | ||
| 94 | uint[](0, 8, 16, 24, 33, 41, 49, 57, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, | ||
| 95 | 173, 181, 189, 198, 206, 214, 222, 231, 239, 247, 255); | ||
| 96 | const uint REPLICATE_1_BIT_TO_6_TABLE[2] = uint[](0, 63); | ||
| 97 | const uint REPLICATE_2_BIT_TO_6_TABLE[4] = uint[](0, 21, 42, 63); | ||
| 98 | const uint REPLICATE_3_BIT_TO_6_TABLE[8] = uint[](0, 9, 18, 27, 36, 45, 54, 63); | ||
| 99 | const uint REPLICATE_4_BIT_TO_6_TABLE[16] = | ||
| 100 | uint[](0, 4, 8, 12, 17, 21, 25, 29, 34, 38, 42, 46, 51, 55, 59, 63); | ||
| 101 | const uint REPLICATE_5_BIT_TO_6_TABLE[32] = | ||
| 102 | uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 33, 35, 37, 39, 41, 43, 45, | ||
| 103 | 47, 49, 51, 53, 55, 57, 59, 61, 63); | ||
| 104 | const uint REPLICATE_6_BIT_TO_8_TABLE[64] = | ||
| 105 | uint[](0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 65, 69, 73, 77, 81, 85, 89, | ||
| 106 | 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, | ||
| 107 | 166, 170, 174, 178, 182, 186, 190, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, | ||
| 108 | 239, 243, 247, 251, 255); | ||
| 109 | const uint REPLICATE_7_BIT_TO_8_TABLE[128] = | ||
| 110 | uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, | ||
| 111 | 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, | ||
| 112 | 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, | ||
| 113 | 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, | ||
| 114 | 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, | ||
| 115 | 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, | ||
| 116 | 237, 239, 241, 243, 245, 247, 249, 251, 253, 255); | ||
| 117 | 64 | ||
| 118 | // Input ASTC texture globals | 65 | // Input ASTC texture globals |
| 119 | uint current_index = 0; | ||
| 120 | int bitsread = 0; | ||
| 121 | int total_bitsread = 0; | 66 | int total_bitsread = 0; |
| 122 | uvec4 local_buff; | 67 | uvec4 local_buff; |
| 123 | 68 | ||
| @@ -125,50 +70,60 @@ uvec4 local_buff; | |||
| 125 | uvec4 color_endpoint_data; | 70 | uvec4 color_endpoint_data; |
| 126 | int color_bitsread = 0; | 71 | int color_bitsread = 0; |
| 127 | 72 | ||
| 128 | // Four values, two endpoints, four maximum partitions | 73 | // Global "vector" to be pushed into when decoding |
| 129 | uint color_values[32]; | 74 | // At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode |
| 130 | int colvals_index = 0; | 75 | // At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode |
| 131 | 76 | // So the maximum would be 144 (12 x 12) elements, x 2 for two planes | |
| 132 | // Weight data globals | 77 | #define DIVCEIL(number, divisor) (number + divisor - 1) / divisor |
| 133 | uvec4 texel_weight_data; | 78 | #define ARRAY_NUM_ELEMENTS 144 |
| 134 | int texel_bitsread = 0; | 79 | #define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4) |
| 80 | uint result_vector[ARRAY_NUM_ELEMENTS * 2]; | ||
| 135 | 81 | ||
| 136 | bool texel_flag = false; | ||
| 137 | |||
| 138 | // Global "vectors" to be pushed into when decoding | ||
| 139 | EncodingData result_vector[144]; | ||
| 140 | int result_index = 0; | 82 | int result_index = 0; |
| 83 | uint result_vector_max_index; | ||
| 84 | bool result_limit_reached = false; | ||
| 141 | 85 | ||
| 142 | EncodingData texel_vector[144]; | 86 | // EncodingData helpers |
| 143 | int texel_vector_index = 0; | 87 | uint Encoding(EncodingData val) { |
| 88 | return bitfieldExtract(val.data, 0, 8); | ||
| 89 | } | ||
| 90 | uint NumBits(EncodingData val) { | ||
| 91 | return bitfieldExtract(val.data, 8, 8); | ||
| 92 | } | ||
| 93 | uint BitValue(EncodingData val) { | ||
| 94 | return bitfieldExtract(val.data, 16, 8); | ||
| 95 | } | ||
| 96 | uint QuintTritValue(EncodingData val) { | ||
| 97 | return bitfieldExtract(val.data, 24, 8); | ||
| 98 | } | ||
| 144 | 99 | ||
| 145 | uint unquantized_texel_weights[2][144]; | 100 | void Encoding(inout EncodingData val, uint v) { |
| 101 | val.data = bitfieldInsert(val.data, v, 0, 8); | ||
| 102 | } | ||
| 103 | void NumBits(inout EncodingData val, uint v) { | ||
| 104 | val.data = bitfieldInsert(val.data, v, 8, 8); | ||
| 105 | } | ||
| 106 | void BitValue(inout EncodingData val, uint v) { | ||
| 107 | val.data = bitfieldInsert(val.data, v, 16, 8); | ||
| 108 | } | ||
| 109 | void QuintTritValue(inout EncodingData val, uint v) { | ||
| 110 | val.data = bitfieldInsert(val.data, v, 24, 8); | ||
| 111 | } | ||
| 146 | 112 | ||
| 147 | uint SwizzleOffset(uvec2 pos) { | 113 | EncodingData CreateEncodingData(uint encoding, uint num_bits, uint bit_val, uint quint_trit_val) { |
| 148 | uint x = pos.x; | 114 | return EncodingData(((encoding) << 0u) | ((num_bits) << 8u) | |
| 149 | uint y = pos.y; | 115 | ((bit_val) << 16u) | ((quint_trit_val) << 24u)); |
| 150 | return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + | ||
| 151 | (y % 2) * 16 + (x % 16); | ||
| 152 | } | 116 | } |
| 153 | 117 | ||
| 154 | // Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)] | 118 | |
| 155 | // is the same as [(num_bits - 1):0] and repeats all the way down. | 119 | void ResultEmplaceBack(EncodingData val) { |
| 156 | uint Replicate(uint val, uint num_bits, uint to_bit) { | 120 | if (result_index >= result_vector_max_index) { |
| 157 | const uint v = val & uint((1 << num_bits) - 1); | 121 | // Alert callers to avoid decoding more than needed by this phase |
| 158 | uint res = v; | 122 | result_limit_reached = true; |
| 159 | uint reslen = num_bits; | 123 | return; |
| 160 | while (reslen < to_bit) { | ||
| 161 | uint comp = 0; | ||
| 162 | if (num_bits > to_bit - reslen) { | ||
| 163 | uint newshift = to_bit - reslen; | ||
| 164 | comp = num_bits - newshift; | ||
| 165 | num_bits = newshift; | ||
| 166 | } | ||
| 167 | res = uint(res << num_bits); | ||
| 168 | res = uint(res | (v >> comp)); | ||
| 169 | reslen += num_bits; | ||
| 170 | } | 124 | } |
| 171 | return res; | 125 | result_vector[result_index] = val.data; |
| 126 | ++result_index; | ||
| 172 | } | 127 | } |
| 173 | 128 | ||
| 174 | uvec4 ReplicateByteTo16(uvec4 value) { | 129 | uvec4 ReplicateByteTo16(uvec4 value) { |
| @@ -176,64 +131,40 @@ uvec4 ReplicateByteTo16(uvec4 value) { | |||
| 176 | } | 131 | } |
| 177 | 132 | ||
| 178 | uint ReplicateBitTo7(uint value) { | 133 | uint ReplicateBitTo7(uint value) { |
| 179 | return REPLICATE_BIT_TO_7_TABLE[value]; | 134 | return value * 127; |
| 180 | } | 135 | } |
| 181 | 136 | ||
| 182 | uint ReplicateBitTo9(uint value) { | 137 | uint ReplicateBitTo9(uint value) { |
| 183 | return REPLICATE_1_BIT_TO_9_TABLE[value]; | 138 | return value * 511; |
| 184 | } | 139 | } |
| 185 | 140 | ||
| 186 | uint FastReplicate(uint value, uint num_bits, uint to_bit) { | 141 | uint ReplicateBits(uint value, uint num_bits, uint to_bit) { |
| 187 | if (num_bits == 0) { | 142 | if (value == 0 || num_bits == 0) { |
| 188 | return 0; | 143 | return 0; |
| 189 | } | 144 | } |
| 190 | if (num_bits == to_bit) { | 145 | if (num_bits >= to_bit) { |
| 191 | return value; | 146 | return value; |
| 192 | } | 147 | } |
| 193 | if (to_bit == 6) { | 148 | const uint v = value & uint((1 << num_bits) - 1); |
| 194 | switch (num_bits) { | 149 | uint res = v; |
| 195 | case 1: | 150 | uint reslen = num_bits; |
| 196 | return REPLICATE_1_BIT_TO_6_TABLE[value]; | 151 | while (reslen < to_bit) { |
| 197 | case 2: | 152 | const uint num_dst_bits_to_shift_up = min(num_bits, to_bit - reslen); |
| 198 | return REPLICATE_2_BIT_TO_6_TABLE[value]; | 153 | const uint num_src_bits_to_shift_down = num_bits - num_dst_bits_to_shift_up; |
| 199 | case 3: | 154 | |
| 200 | return REPLICATE_3_BIT_TO_6_TABLE[value]; | 155 | res <<= num_dst_bits_to_shift_up; |
| 201 | case 4: | 156 | res |= (v >> num_src_bits_to_shift_down); |
| 202 | return REPLICATE_4_BIT_TO_6_TABLE[value]; | 157 | reslen += num_bits; |
| 203 | case 5: | ||
| 204 | return REPLICATE_5_BIT_TO_6_TABLE[value]; | ||
| 205 | default: | ||
| 206 | break; | ||
| 207 | } | ||
| 208 | } else { /* if (to_bit == 8) */ | ||
| 209 | switch (num_bits) { | ||
| 210 | case 1: | ||
| 211 | return REPLICATE_1_BIT_TO_8_TABLE[value]; | ||
| 212 | case 2: | ||
| 213 | return REPLICATE_2_BIT_TO_8_TABLE[value]; | ||
| 214 | case 3: | ||
| 215 | return REPLICATE_3_BIT_TO_8_TABLE[value]; | ||
| 216 | case 4: | ||
| 217 | return REPLICATE_4_BIT_TO_8_TABLE[value]; | ||
| 218 | case 5: | ||
| 219 | return REPLICATE_5_BIT_TO_8_TABLE[value]; | ||
| 220 | case 6: | ||
| 221 | return REPLICATE_6_BIT_TO_8_TABLE[value]; | ||
| 222 | case 7: | ||
| 223 | return REPLICATE_7_BIT_TO_8_TABLE[value]; | ||
| 224 | default: | ||
| 225 | break; | ||
| 226 | } | ||
| 227 | } | 158 | } |
| 228 | return Replicate(value, num_bits, to_bit); | 159 | return res; |
| 229 | } | 160 | } |
| 230 | 161 | ||
| 231 | uint FastReplicateTo8(uint value, uint num_bits) { | 162 | uint FastReplicateTo8(uint value, uint num_bits) { |
| 232 | return FastReplicate(value, num_bits, 8); | 163 | return ReplicateBits(value, num_bits, 8); |
| 233 | } | 164 | } |
| 234 | 165 | ||
| 235 | uint FastReplicateTo6(uint value, uint num_bits) { | 166 | uint FastReplicateTo6(uint value, uint num_bits) { |
| 236 | return FastReplicate(value, num_bits, 6); | 167 | return ReplicateBits(value, num_bits, 6); |
| 237 | } | 168 | } |
| 238 | 169 | ||
| 239 | uint Div3Floor(uint v) { | 170 | uint Div3Floor(uint v) { |
| @@ -266,15 +197,15 @@ uint Hash52(uint p) { | |||
| 266 | return p; | 197 | return p; |
| 267 | } | 198 | } |
| 268 | 199 | ||
| 269 | uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) { | 200 | uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) { |
| 270 | if (small_block) { | 201 | if ((block_dims.y * block_dims.x) < 32) { |
| 271 | x <<= 1; | 202 | x <<= 1; |
| 272 | y <<= 1; | 203 | y <<= 1; |
| 273 | } | 204 | } |
| 274 | 205 | ||
| 275 | seed += (partition_count - 1) * 1024; | 206 | seed += (partition_count - 1) * 1024; |
| 276 | 207 | ||
| 277 | uint rnum = Hash52(uint(seed)); | 208 | const uint rnum = Hash52(uint(seed)); |
| 278 | uint seed1 = uint(rnum & 0xF); | 209 | uint seed1 = uint(rnum & 0xF); |
| 279 | uint seed2 = uint((rnum >> 4) & 0xF); | 210 | uint seed2 = uint((rnum >> 4) & 0xF); |
| 280 | uint seed3 = uint((rnum >> 8) & 0xF); | 211 | uint seed3 = uint((rnum >> 8) & 0xF); |
| @@ -342,53 +273,52 @@ uint ExtractBits(uvec4 payload, int offset, int bits) { | |||
| 342 | if (bits <= 0) { | 273 | if (bits <= 0) { |
| 343 | return 0; | 274 | return 0; |
| 344 | } | 275 | } |
| 345 | int last_offset = offset + bits - 1; | 276 | if (bits > 32) { |
| 346 | int shifted_offset = offset >> 5; | 277 | return 0; |
| 278 | } | ||
| 279 | const int last_offset = offset + bits - 1; | ||
| 280 | const int shifted_offset = offset >> 5; | ||
| 347 | if ((last_offset >> 5) == shifted_offset) { | 281 | if ((last_offset >> 5) == shifted_offset) { |
| 348 | return bitfieldExtract(payload[shifted_offset], offset & 31, bits); | 282 | return bitfieldExtract(payload[shifted_offset], offset & 31, bits); |
| 349 | } | 283 | } |
| 350 | int first_bits = 32 - (offset & 31); | 284 | const int first_bits = 32 - (offset & 31); |
| 351 | int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); | 285 | const int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); |
| 352 | int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); | 286 | const int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); |
| 353 | return result_first | (result_second << first_bits); | 287 | return result_first | (result_second << first_bits); |
| 354 | } | 288 | } |
| 355 | 289 | ||
| 356 | uint StreamBits(uint num_bits) { | 290 | uint StreamBits(uint num_bits) { |
| 357 | int int_bits = int(num_bits); | 291 | const int int_bits = int(num_bits); |
| 358 | uint ret = ExtractBits(local_buff, total_bitsread, int_bits); | 292 | const uint ret = ExtractBits(local_buff, total_bitsread, int_bits); |
| 359 | total_bitsread += int_bits; | 293 | total_bitsread += int_bits; |
| 360 | return ret; | 294 | return ret; |
| 361 | } | 295 | } |
| 362 | 296 | ||
| 297 | void SkipBits(uint num_bits) { | ||
| 298 | const int int_bits = int(num_bits); | ||
| 299 | total_bitsread += int_bits; | ||
| 300 | } | ||
| 301 | |||
| 363 | uint StreamColorBits(uint num_bits) { | 302 | uint StreamColorBits(uint num_bits) { |
| 364 | uint ret = 0; | 303 | const int int_bits = int(num_bits); |
| 365 | int int_bits = int(num_bits); | 304 | const uint ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits); |
| 366 | if (texel_flag) { | 305 | color_bitsread += int_bits; |
| 367 | ret = ExtractBits(texel_weight_data, texel_bitsread, int_bits); | ||
| 368 | texel_bitsread += int_bits; | ||
| 369 | } else { | ||
| 370 | ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits); | ||
| 371 | color_bitsread += int_bits; | ||
| 372 | } | ||
| 373 | return ret; | 306 | return ret; |
| 374 | } | 307 | } |
| 375 | 308 | ||
| 376 | void ResultEmplaceBack(EncodingData val) { | 309 | EncodingData GetEncodingFromVector(uint index) { |
| 377 | if (texel_flag) { | 310 | const uint data = result_vector[index]; |
| 378 | texel_vector[texel_vector_index] = val; | 311 | return EncodingData(data); |
| 379 | ++texel_vector_index; | ||
| 380 | } else { | ||
| 381 | result_vector[result_index] = val; | ||
| 382 | ++result_index; | ||
| 383 | } | ||
| 384 | } | 312 | } |
| 385 | 313 | ||
| 386 | // Returns the number of bits required to encode n_vals values. | 314 | // Returns the number of bits required to encode n_vals values. |
| 387 | uint GetBitLength(uint n_vals, uint encoding_index) { | 315 | uint GetBitLength(uint n_vals, uint encoding_index) { |
| 388 | uint total_bits = encoding_values[encoding_index].num_bits * n_vals; | 316 | const EncodingData encoding_value = EncodingData(encoding_values[encoding_index]); |
| 389 | if (encoding_values[encoding_index].encoding == TRIT) { | 317 | const uint encoding = Encoding(encoding_value); |
| 318 | uint total_bits = NumBits(encoding_value) * n_vals; | ||
| 319 | if (encoding == TRIT) { | ||
| 390 | total_bits += Div5Ceil(n_vals * 8); | 320 | total_bits += Div5Ceil(n_vals * 8); |
| 391 | } else if (encoding_values[encoding_index].encoding == QUINT) { | 321 | } else if (encoding == QUINT) { |
| 392 | total_bits += Div3Ceil(n_vals * 7); | 322 | total_bits += Div3Ceil(n_vals * 7); |
| 393 | } | 323 | } |
| 394 | return total_bits; | 324 | return total_bits; |
| @@ -403,7 +333,7 @@ uint GetNumWeightValues(uvec2 size, bool dual_plane) { | |||
| 403 | } | 333 | } |
| 404 | 334 | ||
| 405 | uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { | 335 | uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { |
| 406 | uint n_vals = GetNumWeightValues(size, dual_plane); | 336 | const uint n_vals = GetNumWeightValues(size, dual_plane); |
| 407 | return GetBitLength(n_vals, max_weight); | 337 | return GetBitLength(n_vals, max_weight); |
| 408 | } | 338 | } |
| 409 | 339 | ||
| @@ -412,87 +342,74 @@ uint BitsBracket(uint bits, uint pos) { | |||
| 412 | } | 342 | } |
| 413 | 343 | ||
| 414 | uint BitsOp(uint bits, uint start, uint end) { | 344 | uint BitsOp(uint bits, uint start, uint end) { |
| 415 | if (start == end) { | 345 | const uint mask = (1 << (end - start + 1)) - 1; |
| 416 | return BitsBracket(bits, start); | ||
| 417 | } else if (start > end) { | ||
| 418 | uint t = start; | ||
| 419 | start = end; | ||
| 420 | end = t; | ||
| 421 | } | ||
| 422 | |||
| 423 | uint mask = (1 << (end - start + 1)) - 1; | ||
| 424 | return ((bits >> start) & mask); | 346 | return ((bits >> start) & mask); |
| 425 | } | 347 | } |
| 426 | 348 | ||
| 427 | void DecodeQuintBlock(uint num_bits) { | 349 | void DecodeQuintBlock(uint num_bits) { |
| 428 | uint m[3]; | 350 | uvec3 m; |
| 429 | uint q[3]; | 351 | uvec4 qQ; |
| 430 | uint Q; | ||
| 431 | m[0] = StreamColorBits(num_bits); | 352 | m[0] = StreamColorBits(num_bits); |
| 432 | Q = StreamColorBits(3); | 353 | qQ.w = StreamColorBits(3); |
| 433 | m[1] = StreamColorBits(num_bits); | 354 | m[1] = StreamColorBits(num_bits); |
| 434 | Q |= StreamColorBits(2) << 3; | 355 | qQ.w |= StreamColorBits(2) << 3; |
| 435 | m[2] = StreamColorBits(num_bits); | 356 | m[2] = StreamColorBits(num_bits); |
| 436 | Q |= StreamColorBits(2) << 5; | 357 | qQ.w |= StreamColorBits(2) << 5; |
| 437 | if (BitsOp(Q, 1, 2) == 3 && BitsOp(Q, 5, 6) == 0) { | 358 | if (BitsOp(qQ.w, 1, 2) == 3 && BitsOp(qQ.w, 5, 6) == 0) { |
| 438 | q[0] = 4; | 359 | qQ.x = 4; |
| 439 | q[1] = 4; | 360 | qQ.y = 4; |
| 440 | q[2] = (BitsBracket(Q, 0) << 2) | ((BitsBracket(Q, 4) & ~BitsBracket(Q, 0)) << 1) | | 361 | qQ.z = (BitsBracket(qQ.w, 0) << 2) | ((BitsBracket(qQ.w, 4) & ~BitsBracket(qQ.w, 0)) << 1) | |
| 441 | (BitsBracket(Q, 3) & ~BitsBracket(Q, 0)); | 362 | (BitsBracket(qQ.w, 3) & ~BitsBracket(qQ.w, 0)); |
| 442 | } else { | 363 | } else { |
| 443 | uint C = 0; | 364 | uint C = 0; |
| 444 | if (BitsOp(Q, 1, 2) == 3) { | 365 | if (BitsOp(qQ.w, 1, 2) == 3) { |
| 445 | q[2] = 4; | 366 | qQ.z = 4; |
| 446 | C = (BitsOp(Q, 3, 4) << 3) | ((~BitsOp(Q, 5, 6) & 3) << 1) | BitsBracket(Q, 0); | 367 | C = (BitsOp(qQ.w, 3, 4) << 3) | ((~BitsOp(qQ.w, 5, 6) & 3) << 1) | BitsBracket(qQ.w, 0); |
| 447 | } else { | 368 | } else { |
| 448 | q[2] = BitsOp(Q, 5, 6); | 369 | qQ.z = BitsOp(qQ.w, 5, 6); |
| 449 | C = BitsOp(Q, 0, 4); | 370 | C = BitsOp(qQ.w, 0, 4); |
| 450 | } | 371 | } |
| 451 | if (BitsOp(C, 0, 2) == 5) { | 372 | if (BitsOp(C, 0, 2) == 5) { |
| 452 | q[1] = 4; | 373 | qQ.y = 4; |
| 453 | q[0] = BitsOp(C, 3, 4); | 374 | qQ.x = BitsOp(C, 3, 4); |
| 454 | } else { | 375 | } else { |
| 455 | q[1] = BitsOp(C, 3, 4); | 376 | qQ.y = BitsOp(C, 3, 4); |
| 456 | q[0] = BitsOp(C, 0, 2); | 377 | qQ.x = BitsOp(C, 0, 2); |
| 457 | } | 378 | } |
| 458 | } | 379 | } |
| 459 | for (uint i = 0; i < 3; i++) { | 380 | for (uint i = 0; i < 3; i++) { |
| 460 | EncodingData val; | 381 | const EncodingData val = CreateEncodingData(QUINT, num_bits, m[i], qQ[i]); |
| 461 | val.encoding = QUINT; | ||
| 462 | val.num_bits = num_bits; | ||
| 463 | val.bit_value = m[i]; | ||
| 464 | val.quint_trit_value = q[i]; | ||
| 465 | ResultEmplaceBack(val); | 382 | ResultEmplaceBack(val); |
| 466 | } | 383 | } |
| 467 | } | 384 | } |
| 468 | 385 | ||
| 469 | void DecodeTritBlock(uint num_bits) { | 386 | void DecodeTritBlock(uint num_bits) { |
| 470 | uint m[5]; | 387 | uvec4 m; |
| 471 | uint t[5]; | 388 | uvec4 t; |
| 472 | uint T; | 389 | uvec3 Tm5t5; |
| 473 | m[0] = StreamColorBits(num_bits); | 390 | m[0] = StreamColorBits(num_bits); |
| 474 | T = StreamColorBits(2); | 391 | Tm5t5.x = StreamColorBits(2); |
| 475 | m[1] = StreamColorBits(num_bits); | 392 | m[1] = StreamColorBits(num_bits); |
| 476 | T |= StreamColorBits(2) << 2; | 393 | Tm5t5.x |= StreamColorBits(2) << 2; |
| 477 | m[2] = StreamColorBits(num_bits); | 394 | m[2] = StreamColorBits(num_bits); |
| 478 | T |= StreamColorBits(1) << 4; | 395 | Tm5t5.x |= StreamColorBits(1) << 4; |
| 479 | m[3] = StreamColorBits(num_bits); | 396 | m[3] = StreamColorBits(num_bits); |
| 480 | T |= StreamColorBits(2) << 5; | 397 | Tm5t5.x |= StreamColorBits(2) << 5; |
| 481 | m[4] = StreamColorBits(num_bits); | 398 | Tm5t5.y = StreamColorBits(num_bits); |
| 482 | T |= StreamColorBits(1) << 7; | 399 | Tm5t5.x |= StreamColorBits(1) << 7; |
| 483 | uint C = 0; | 400 | uint C = 0; |
| 484 | if (BitsOp(T, 2, 4) == 7) { | 401 | if (BitsOp(Tm5t5.x, 2, 4) == 7) { |
| 485 | C = (BitsOp(T, 5, 7) << 2) | BitsOp(T, 0, 1); | 402 | C = (BitsOp(Tm5t5.x, 5, 7) << 2) | BitsOp(Tm5t5.x, 0, 1); |
| 486 | t[4] = 2; | 403 | Tm5t5.z = 2; |
| 487 | t[3] = 2; | 404 | t[3] = 2; |
| 488 | } else { | 405 | } else { |
| 489 | C = BitsOp(T, 0, 4); | 406 | C = BitsOp(Tm5t5.x, 0, 4); |
| 490 | if (BitsOp(T, 5, 6) == 3) { | 407 | if (BitsOp(Tm5t5.x, 5, 6) == 3) { |
| 491 | t[4] = 2; | 408 | Tm5t5.z = 2; |
| 492 | t[3] = BitsBracket(T, 7); | 409 | t[3] = BitsBracket(Tm5t5.x, 7); |
| 493 | } else { | 410 | } else { |
| 494 | t[4] = BitsBracket(T, 7); | 411 | Tm5t5.z = BitsBracket(Tm5t5.x, 7); |
| 495 | t[3] = BitsOp(T, 5, 6); | 412 | t[3] = BitsOp(Tm5t5.x, 5, 6); |
| 496 | } | 413 | } |
| 497 | } | 414 | } |
| 498 | if (BitsOp(C, 0, 1) == 3) { | 415 | if (BitsOp(C, 0, 1) == 3) { |
| @@ -508,31 +425,31 @@ void DecodeTritBlock(uint num_bits) { | |||
| 508 | t[1] = BitsOp(C, 2, 3); | 425 | t[1] = BitsOp(C, 2, 3); |
| 509 | t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1)); | 426 | t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1)); |
| 510 | } | 427 | } |
| 511 | for (uint i = 0; i < 5; i++) { | 428 | for (uint i = 0; i < 4; i++) { |
| 512 | EncodingData val; | 429 | const EncodingData val = CreateEncodingData(TRIT, num_bits, m[i], t[i]); |
| 513 | val.encoding = TRIT; | ||
| 514 | val.num_bits = num_bits; | ||
| 515 | val.bit_value = m[i]; | ||
| 516 | val.quint_trit_value = t[i]; | ||
| 517 | ResultEmplaceBack(val); | 430 | ResultEmplaceBack(val); |
| 518 | } | 431 | } |
| 432 | const EncodingData val = CreateEncodingData(TRIT, num_bits, Tm5t5.y, Tm5t5.z); | ||
| 433 | ResultEmplaceBack(val); | ||
| 519 | } | 434 | } |
| 520 | 435 | ||
| 521 | void DecodeIntegerSequence(uint max_range, uint num_values) { | 436 | void DecodeIntegerSequence(uint max_range, uint num_values) { |
| 522 | EncodingData val = encoding_values[max_range]; | 437 | EncodingData val = EncodingData(encoding_values[max_range]); |
| 438 | const uint encoding = Encoding(val); | ||
| 439 | const uint num_bits = NumBits(val); | ||
| 523 | uint vals_decoded = 0; | 440 | uint vals_decoded = 0; |
| 524 | while (vals_decoded < num_values) { | 441 | while (vals_decoded < num_values && !result_limit_reached) { |
| 525 | switch (val.encoding) { | 442 | switch (encoding) { |
| 526 | case QUINT: | 443 | case QUINT: |
| 527 | DecodeQuintBlock(val.num_bits); | 444 | DecodeQuintBlock(num_bits); |
| 528 | vals_decoded += 3; | 445 | vals_decoded += 3; |
| 529 | break; | 446 | break; |
| 530 | case TRIT: | 447 | case TRIT: |
| 531 | DecodeTritBlock(val.num_bits); | 448 | DecodeTritBlock(num_bits); |
| 532 | vals_decoded += 5; | 449 | vals_decoded += 5; |
| 533 | break; | 450 | break; |
| 534 | case JUST_BITS: | 451 | case JUST_BITS: |
| 535 | val.bit_value = StreamColorBits(val.num_bits); | 452 | BitValue(val, StreamColorBits(num_bits)); |
| 536 | ResultEmplaceBack(val); | 453 | ResultEmplaceBack(val); |
| 537 | vals_decoded++; | 454 | vals_decoded++; |
| 538 | break; | 455 | break; |
| @@ -540,7 +457,7 @@ void DecodeIntegerSequence(uint max_range, uint num_values) { | |||
| 540 | } | 457 | } |
| 541 | } | 458 | } |
| 542 | 459 | ||
| 543 | void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | 460 | void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, out uint color_values[32]) { |
| 544 | uint num_values = 0; | 461 | uint num_values = 0; |
| 545 | for (uint i = 0; i < num_partitions; i++) { | 462 | for (uint i = 0; i < num_partitions; i++) { |
| 546 | num_values += ((modes[i] >> 2) + 1) << 1; | 463 | num_values += ((modes[i] >> 2) + 1) << 1; |
| @@ -549,7 +466,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 549 | // TODO(ameerj): profile with binary search | 466 | // TODO(ameerj): profile with binary search |
| 550 | int range = 0; | 467 | int range = 0; |
| 551 | while (++range < encoding_values.length()) { | 468 | while (++range < encoding_values.length()) { |
| 552 | uint bit_length = GetBitLength(num_values, range); | 469 | const uint bit_length = GetBitLength(num_values, range); |
| 553 | if (bit_length > color_data_bits) { | 470 | if (bit_length > color_data_bits) { |
| 554 | break; | 471 | break; |
| 555 | } | 472 | } |
| @@ -560,48 +477,49 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 560 | if (out_index >= num_values) { | 477 | if (out_index >= num_values) { |
| 561 | break; | 478 | break; |
| 562 | } | 479 | } |
| 563 | EncodingData val = result_vector[itr]; | 480 | const EncodingData val = GetEncodingFromVector(itr); |
| 564 | uint bitlen = val.num_bits; | 481 | const uint encoding = Encoding(val); |
| 565 | uint bitval = val.bit_value; | 482 | const uint bitlen = NumBits(val); |
| 483 | const uint bitval = BitValue(val); | ||
| 566 | uint A = 0, B = 0, C = 0, D = 0; | 484 | uint A = 0, B = 0, C = 0, D = 0; |
| 567 | A = ReplicateBitTo9((bitval & 1)); | 485 | A = ReplicateBitTo9((bitval & 1)); |
| 568 | switch (val.encoding) { | 486 | switch (encoding) { |
| 569 | case JUST_BITS: | 487 | case JUST_BITS: |
| 570 | color_values[out_index++] = FastReplicateTo8(bitval, bitlen); | 488 | color_values[++out_index] = FastReplicateTo8(bitval, bitlen); |
| 571 | break; | 489 | break; |
| 572 | case TRIT: { | 490 | case TRIT: { |
| 573 | D = val.quint_trit_value; | 491 | D = QuintTritValue(val); |
| 574 | switch (bitlen) { | 492 | switch (bitlen) { |
| 575 | case 1: | 493 | case 1: |
| 576 | C = 204; | 494 | C = 204; |
| 577 | break; | 495 | break; |
| 578 | case 2: { | 496 | case 2: { |
| 579 | C = 93; | 497 | C = 93; |
| 580 | uint b = (bitval >> 1) & 1; | 498 | const uint b = (bitval >> 1) & 1; |
| 581 | B = (b << 8) | (b << 4) | (b << 2) | (b << 1); | 499 | B = (b << 8) | (b << 4) | (b << 2) | (b << 1); |
| 582 | break; | 500 | break; |
| 583 | } | 501 | } |
| 584 | case 3: { | 502 | case 3: { |
| 585 | C = 44; | 503 | C = 44; |
| 586 | uint cb = (bitval >> 1) & 3; | 504 | const uint cb = (bitval >> 1) & 3; |
| 587 | B = (cb << 7) | (cb << 2) | cb; | 505 | B = (cb << 7) | (cb << 2) | cb; |
| 588 | break; | 506 | break; |
| 589 | } | 507 | } |
| 590 | case 4: { | 508 | case 4: { |
| 591 | C = 22; | 509 | C = 22; |
| 592 | uint dcb = (bitval >> 1) & 7; | 510 | const uint dcb = (bitval >> 1) & 7; |
| 593 | B = (dcb << 6) | dcb; | 511 | B = (dcb << 6) | dcb; |
| 594 | break; | 512 | break; |
| 595 | } | 513 | } |
| 596 | case 5: { | 514 | case 5: { |
| 597 | C = 11; | 515 | C = 11; |
| 598 | uint edcb = (bitval >> 1) & 0xF; | 516 | const uint edcb = (bitval >> 1) & 0xF; |
| 599 | B = (edcb << 5) | (edcb >> 2); | 517 | B = (edcb << 5) | (edcb >> 2); |
| 600 | break; | 518 | break; |
| 601 | } | 519 | } |
| 602 | case 6: { | 520 | case 6: { |
| 603 | C = 5; | 521 | C = 5; |
| 604 | uint fedcb = (bitval >> 1) & 0x1F; | 522 | const uint fedcb = (bitval >> 1) & 0x1F; |
| 605 | B = (fedcb << 4) | (fedcb >> 4); | 523 | B = (fedcb << 4) | (fedcb >> 4); |
| 606 | break; | 524 | break; |
| 607 | } | 525 | } |
| @@ -609,32 +527,32 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 609 | break; | 527 | break; |
| 610 | } | 528 | } |
| 611 | case QUINT: { | 529 | case QUINT: { |
| 612 | D = val.quint_trit_value; | 530 | D = QuintTritValue(val); |
| 613 | switch (bitlen) { | 531 | switch (bitlen) { |
| 614 | case 1: | 532 | case 1: |
| 615 | C = 113; | 533 | C = 113; |
| 616 | break; | 534 | break; |
| 617 | case 2: { | 535 | case 2: { |
| 618 | C = 54; | 536 | C = 54; |
| 619 | uint b = (bitval >> 1) & 1; | 537 | const uint b = (bitval >> 1) & 1; |
| 620 | B = (b << 8) | (b << 3) | (b << 2); | 538 | B = (b << 8) | (b << 3) | (b << 2); |
| 621 | break; | 539 | break; |
| 622 | } | 540 | } |
| 623 | case 3: { | 541 | case 3: { |
| 624 | C = 26; | 542 | C = 26; |
| 625 | uint cb = (bitval >> 1) & 3; | 543 | const uint cb = (bitval >> 1) & 3; |
| 626 | B = (cb << 7) | (cb << 1) | (cb >> 1); | 544 | B = (cb << 7) | (cb << 1) | (cb >> 1); |
| 627 | break; | 545 | break; |
| 628 | } | 546 | } |
| 629 | case 4: { | 547 | case 4: { |
| 630 | C = 13; | 548 | C = 13; |
| 631 | uint dcb = (bitval >> 1) & 7; | 549 | const uint dcb = (bitval >> 1) & 7; |
| 632 | B = (dcb << 6) | (dcb >> 1); | 550 | B = (dcb << 6) | (dcb >> 1); |
| 633 | break; | 551 | break; |
| 634 | } | 552 | } |
| 635 | case 5: { | 553 | case 5: { |
| 636 | C = 6; | 554 | C = 6; |
| 637 | uint edcb = (bitval >> 1) & 0xF; | 555 | const uint edcb = (bitval >> 1) & 0xF; |
| 638 | B = (edcb << 5) | (edcb >> 3); | 556 | B = (edcb << 5) | (edcb >> 3); |
| 639 | break; | 557 | break; |
| 640 | } | 558 | } |
| @@ -642,11 +560,11 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 642 | break; | 560 | break; |
| 643 | } | 561 | } |
| 644 | } | 562 | } |
| 645 | if (val.encoding != JUST_BITS) { | 563 | if (encoding != JUST_BITS) { |
| 646 | uint T = (D * C) + B; | 564 | uint T = (D * C) + B; |
| 647 | T ^= A; | 565 | T ^= A; |
| 648 | T = (A & 0x80) | (T >> 2); | 566 | T = (A & 0x80) | (T >> 2); |
| 649 | color_values[out_index++] = T; | 567 | color_values[++out_index] = T; |
| 650 | } | 568 | } |
| 651 | } | 569 | } |
| 652 | } | 570 | } |
| @@ -664,139 +582,136 @@ ivec2 BitTransferSigned(int a, int b) { | |||
| 664 | } | 582 | } |
| 665 | 583 | ||
| 666 | uvec4 ClampByte(ivec4 color) { | 584 | uvec4 ClampByte(ivec4 color) { |
| 667 | for (uint i = 0; i < 4; ++i) { | 585 | return uvec4(clamp(color, 0, 255)); |
| 668 | color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]); | ||
| 669 | } | ||
| 670 | return uvec4(color); | ||
| 671 | } | 586 | } |
| 672 | 587 | ||
| 673 | ivec4 BlueContract(int a, int r, int g, int b) { | 588 | ivec4 BlueContract(int a, int r, int g, int b) { |
| 674 | return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); | 589 | return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); |
| 675 | } | 590 | } |
| 676 | 591 | ||
| 677 | void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { | 592 | void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32], |
| 593 | inout uint colvals_index) { | ||
| 678 | #define READ_UINT_VALUES(N) \ | 594 | #define READ_UINT_VALUES(N) \ |
| 679 | uint v[N]; \ | 595 | uvec4 V[2]; \ |
| 680 | for (uint i = 0; i < N; i++) { \ | 596 | for (uint i = 0; i < N; i++) { \ |
| 681 | v[i] = color_values[colvals_index++]; \ | 597 | V[i / 4][i % 4] = color_values[++colvals_index]; \ |
| 682 | } | 598 | } |
| 683 | |||
| 684 | #define READ_INT_VALUES(N) \ | 599 | #define READ_INT_VALUES(N) \ |
| 685 | int v[N]; \ | 600 | ivec4 V[2]; \ |
| 686 | for (uint i = 0; i < N; i++) { \ | 601 | for (uint i = 0; i < N; i++) { \ |
| 687 | v[i] = int(color_values[colvals_index++]); \ | 602 | V[i / 4][i % 4] = int(color_values[++colvals_index]); \ |
| 688 | } | 603 | } |
| 689 | 604 | ||
| 690 | switch (color_endpoint_mode) { | 605 | switch (color_endpoint_mode) { |
| 691 | case 0: { | 606 | case 0: { |
| 692 | READ_UINT_VALUES(2) | 607 | READ_UINT_VALUES(2) |
| 693 | ep1 = uvec4(0xFF, v[0], v[0], v[0]); | 608 | ep1 = uvec4(0xFF, V[0].x, V[0].x, V[0].x); |
| 694 | ep2 = uvec4(0xFF, v[1], v[1], v[1]); | 609 | ep2 = uvec4(0xFF, V[0].y, V[0].y, V[0].y); |
| 695 | break; | 610 | break; |
| 696 | } | 611 | } |
| 697 | case 1: { | 612 | case 1: { |
| 698 | READ_UINT_VALUES(2) | 613 | READ_UINT_VALUES(2) |
| 699 | uint L0 = (v[0] >> 2) | (v[1] & 0xC0); | 614 | const uint L0 = (V[0].x >> 2) | (V[0].y & 0xC0); |
| 700 | uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU); | 615 | const uint L1 = min(L0 + (V[0].y & 0x3F), 0xFFU); |
| 701 | ep1 = uvec4(0xFF, L0, L0, L0); | 616 | ep1 = uvec4(0xFF, L0, L0, L0); |
| 702 | ep2 = uvec4(0xFF, L1, L1, L1); | 617 | ep2 = uvec4(0xFF, L1, L1, L1); |
| 703 | break; | 618 | break; |
| 704 | } | 619 | } |
| 705 | case 4: { | 620 | case 4: { |
| 706 | READ_UINT_VALUES(4) | 621 | READ_UINT_VALUES(4) |
| 707 | ep1 = uvec4(v[2], v[0], v[0], v[0]); | 622 | ep1 = uvec4(V[0].z, V[0].x, V[0].x, V[0].x); |
| 708 | ep2 = uvec4(v[3], v[1], v[1], v[1]); | 623 | ep2 = uvec4(V[0].w, V[0].y, V[0].y, V[0].y); |
| 709 | break; | 624 | break; |
| 710 | } | 625 | } |
| 711 | case 5: { | 626 | case 5: { |
| 712 | READ_INT_VALUES(4) | 627 | READ_INT_VALUES(4) |
| 713 | ivec2 transferred = BitTransferSigned(v[1], v[0]); | 628 | ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); |
| 714 | v[1] = transferred.x; | 629 | V[0].y = transferred.x; |
| 715 | v[0] = transferred.y; | 630 | V[0].x = transferred.y; |
| 716 | transferred = BitTransferSigned(v[3], v[2]); | 631 | transferred = BitTransferSigned(V[0].w, V[0].z); |
| 717 | v[3] = transferred.x; | 632 | V[0].w = transferred.x; |
| 718 | v[2] = transferred.y; | 633 | V[0].z = transferred.y; |
| 719 | ep1 = ClampByte(ivec4(v[2], v[0], v[0], v[0])); | 634 | ep1 = ClampByte(ivec4(V[0].z, V[0].x, V[0].x, V[0].x)); |
| 720 | ep2 = ClampByte(ivec4(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1])); | 635 | ep2 = ClampByte(ivec4(V[0].z + V[0].w, V[0].x + V[0].y, V[0].x + V[0].y, V[0].x + V[0].y)); |
| 721 | break; | 636 | break; |
| 722 | } | 637 | } |
| 723 | case 6: { | 638 | case 6: { |
| 724 | READ_UINT_VALUES(4) | 639 | READ_UINT_VALUES(4) |
| 725 | ep1 = uvec4(0xFF, (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8); | 640 | ep1 = uvec4(0xFF, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8); |
| 726 | ep2 = uvec4(0xFF, v[0], v[1], v[2]); | 641 | ep2 = uvec4(0xFF, V[0].x, V[0].y, V[0].z); |
| 727 | break; | 642 | break; |
| 728 | } | 643 | } |
| 729 | case 8: { | 644 | case 8: { |
| 730 | READ_UINT_VALUES(6) | 645 | READ_UINT_VALUES(6) |
| 731 | if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) { | 646 | if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) { |
| 732 | ep1 = uvec4(0xFF, v[0], v[2], v[4]); | 647 | ep1 = uvec4(0xFF, V[0].x, V[0].z, V[1].x); |
| 733 | ep2 = uvec4(0xFF, v[1], v[3], v[5]); | 648 | ep2 = uvec4(0xFF, V[0].y, V[0].w, V[1].y); |
| 734 | } else { | 649 | } else { |
| 735 | ep1 = uvec4(BlueContract(0xFF, int(v[1]), int(v[3]), int(v[5]))); | 650 | ep1 = uvec4(BlueContract(0xFF, int(V[0].y), int(V[0].w), int(V[1].y))); |
| 736 | ep2 = uvec4(BlueContract(0xFF, int(v[0]), int(v[2]), int(v[4]))); | 651 | ep2 = uvec4(BlueContract(0xFF, int(V[0].x), int(V[0].z), int(V[1].x))); |
| 737 | } | 652 | } |
| 738 | break; | 653 | break; |
| 739 | } | 654 | } |
| 740 | case 9: { | 655 | case 9: { |
| 741 | READ_INT_VALUES(6) | 656 | READ_INT_VALUES(6) |
| 742 | ivec2 transferred = BitTransferSigned(v[1], v[0]); | 657 | ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); |
| 743 | v[1] = transferred.x; | 658 | V[0].y = transferred.x; |
| 744 | v[0] = transferred.y; | 659 | V[0].x = transferred.y; |
| 745 | transferred = BitTransferSigned(v[3], v[2]); | 660 | transferred = BitTransferSigned(V[0].w, V[0].z); |
| 746 | v[3] = transferred.x; | 661 | V[0].w = transferred.x; |
| 747 | v[2] = transferred.y; | 662 | V[0].z = transferred.y; |
| 748 | transferred = BitTransferSigned(v[5], v[4]); | 663 | transferred = BitTransferSigned(V[1].y, V[1].x); |
| 749 | v[5] = transferred.x; | 664 | V[1].y = transferred.x; |
| 750 | v[4] = transferred.y; | 665 | V[1].x = transferred.y; |
| 751 | if ((v[1] + v[3] + v[5]) >= 0) { | 666 | if ((V[0].y + V[0].w + V[1].y) >= 0) { |
| 752 | ep1 = ClampByte(ivec4(0xFF, v[0], v[2], v[4])); | 667 | ep1 = ClampByte(ivec4(0xFF, V[0].x, V[0].z, V[1].x)); |
| 753 | ep2 = ClampByte(ivec4(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 668 | ep2 = ClampByte(ivec4(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 754 | } else { | 669 | } else { |
| 755 | ep1 = ClampByte(BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 670 | ep1 = ClampByte(BlueContract(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 756 | ep2 = ClampByte(BlueContract(0xFF, v[0], v[2], v[4])); | 671 | ep2 = ClampByte(BlueContract(0xFF, V[0].x, V[0].z, V[1].x)); |
| 757 | } | 672 | } |
| 758 | break; | 673 | break; |
| 759 | } | 674 | } |
| 760 | case 10: { | 675 | case 10: { |
| 761 | READ_UINT_VALUES(6) | 676 | READ_UINT_VALUES(6) |
| 762 | ep1 = uvec4(v[4], (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8); | 677 | ep1 = uvec4(V[1].x, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8); |
| 763 | ep2 = uvec4(v[5], v[0], v[1], v[2]); | 678 | ep2 = uvec4(V[1].y, V[0].x, V[0].y, V[0].z); |
| 764 | break; | 679 | break; |
| 765 | } | 680 | } |
| 766 | case 12: { | 681 | case 12: { |
| 767 | READ_UINT_VALUES(8) | 682 | READ_UINT_VALUES(8) |
| 768 | if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) { | 683 | if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) { |
| 769 | ep1 = uvec4(v[6], v[0], v[2], v[4]); | 684 | ep1 = uvec4(V[1].z, V[0].x, V[0].z, V[1].x); |
| 770 | ep2 = uvec4(v[7], v[1], v[3], v[5]); | 685 | ep2 = uvec4(V[1].w, V[0].y, V[0].w, V[1].y); |
| 771 | } else { | 686 | } else { |
| 772 | ep1 = uvec4(BlueContract(int(v[7]), int(v[1]), int(v[3]), int(v[5]))); | 687 | ep1 = uvec4(BlueContract(int(V[1].w), int(V[0].y), int(V[0].w), int(V[1].y))); |
| 773 | ep2 = uvec4(BlueContract(int(v[6]), int(v[0]), int(v[2]), int(v[4]))); | 688 | ep2 = uvec4(BlueContract(int(V[1].z), int(V[0].x), int(V[0].z), int(V[1].x))); |
| 774 | } | 689 | } |
| 775 | break; | 690 | break; |
| 776 | } | 691 | } |
| 777 | case 13: { | 692 | case 13: { |
| 778 | READ_INT_VALUES(8) | 693 | READ_INT_VALUES(8) |
| 779 | ivec2 transferred = BitTransferSigned(v[1], v[0]); | 694 | ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); |
| 780 | v[1] = transferred.x; | 695 | V[0].y = transferred.x; |
| 781 | v[0] = transferred.y; | 696 | V[0].x = transferred.y; |
| 782 | transferred = BitTransferSigned(v[3], v[2]); | 697 | transferred = BitTransferSigned(V[0].w, V[0].z); |
| 783 | v[3] = transferred.x; | 698 | V[0].w = transferred.x; |
| 784 | v[2] = transferred.y; | 699 | V[0].z = transferred.y; |
| 785 | 700 | ||
| 786 | transferred = BitTransferSigned(v[5], v[4]); | 701 | transferred = BitTransferSigned(V[1].y, V[1].x); |
| 787 | v[5] = transferred.x; | 702 | V[1].y = transferred.x; |
| 788 | v[4] = transferred.y; | 703 | V[1].x = transferred.y; |
| 789 | 704 | ||
| 790 | transferred = BitTransferSigned(v[7], v[6]); | 705 | transferred = BitTransferSigned(V[1].w, V[1].z); |
| 791 | v[7] = transferred.x; | 706 | V[1].w = transferred.x; |
| 792 | v[6] = transferred.y; | 707 | V[1].z = transferred.y; |
| 793 | 708 | ||
| 794 | if ((v[1] + v[3] + v[5]) >= 0) { | 709 | if ((V[0].y + V[0].w + V[1].y) >= 0) { |
| 795 | ep1 = ClampByte(ivec4(v[6], v[0], v[2], v[4])); | 710 | ep1 = ClampByte(ivec4(V[1].z, V[0].x, V[0].z, V[1].x)); |
| 796 | ep2 = ClampByte(ivec4(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 711 | ep2 = ClampByte(ivec4(V[1].w + V[1].z, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 797 | } else { | 712 | } else { |
| 798 | ep1 = ClampByte(BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 713 | ep1 = ClampByte(BlueContract(V[1].z + V[1].w, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 799 | ep2 = ClampByte(BlueContract(v[6], v[0], v[2], v[4])); | 714 | ep2 = ClampByte(BlueContract(V[1].z, V[0].x, V[0].z, V[1].x)); |
| 800 | } | 715 | } |
| 801 | break; | 716 | break; |
| 802 | } | 717 | } |
| @@ -812,36 +727,34 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { | |||
| 812 | } | 727 | } |
| 813 | 728 | ||
| 814 | uint UnquantizeTexelWeight(EncodingData val) { | 729 | uint UnquantizeTexelWeight(EncodingData val) { |
| 815 | uint bitval = val.bit_value; | 730 | const uint encoding = Encoding(val); |
| 816 | uint bitlen = val.num_bits; | 731 | const uint bitlen = NumBits(val); |
| 817 | uint A = ReplicateBitTo7((bitval & 1)); | 732 | const uint bitval = BitValue(val); |
| 733 | const uint A = ReplicateBitTo7((bitval & 1)); | ||
| 818 | uint B = 0, C = 0, D = 0; | 734 | uint B = 0, C = 0, D = 0; |
| 819 | uint result = 0; | 735 | uint result = 0; |
| 820 | switch (val.encoding) { | 736 | const uint bitlen_0_results[5] = {0, 16, 32, 48, 64}; |
| 737 | switch (encoding) { | ||
| 821 | case JUST_BITS: | 738 | case JUST_BITS: |
| 822 | result = FastReplicateTo6(bitval, bitlen); | 739 | return FastReplicateTo6(bitval, bitlen); |
| 823 | break; | ||
| 824 | case TRIT: { | 740 | case TRIT: { |
| 825 | D = val.quint_trit_value; | 741 | D = QuintTritValue(val); |
| 826 | switch (bitlen) { | 742 | switch (bitlen) { |
| 827 | case 0: { | 743 | case 0: |
| 828 | uint results[3] = {0, 32, 63}; | 744 | return bitlen_0_results[D * 2]; |
| 829 | result = results[D]; | ||
| 830 | break; | ||
| 831 | } | ||
| 832 | case 1: { | 745 | case 1: { |
| 833 | C = 50; | 746 | C = 50; |
| 834 | break; | 747 | break; |
| 835 | } | 748 | } |
| 836 | case 2: { | 749 | case 2: { |
| 837 | C = 23; | 750 | C = 23; |
| 838 | uint b = (bitval >> 1) & 1; | 751 | const uint b = (bitval >> 1) & 1; |
| 839 | B = (b << 6) | (b << 2) | b; | 752 | B = (b << 6) | (b << 2) | b; |
| 840 | break; | 753 | break; |
| 841 | } | 754 | } |
| 842 | case 3: { | 755 | case 3: { |
| 843 | C = 11; | 756 | C = 11; |
| 844 | uint cb = (bitval >> 1) & 3; | 757 | const uint cb = (bitval >> 1) & 3; |
| 845 | B = (cb << 5) | cb; | 758 | B = (cb << 5) | cb; |
| 846 | break; | 759 | break; |
| 847 | } | 760 | } |
| @@ -851,20 +764,17 @@ uint UnquantizeTexelWeight(EncodingData val) { | |||
| 851 | break; | 764 | break; |
| 852 | } | 765 | } |
| 853 | case QUINT: { | 766 | case QUINT: { |
| 854 | D = val.quint_trit_value; | 767 | D = QuintTritValue(val); |
| 855 | switch (bitlen) { | 768 | switch (bitlen) { |
| 856 | case 0: { | 769 | case 0: |
| 857 | uint results[5] = {0, 16, 32, 47, 63}; | 770 | return bitlen_0_results[D]; |
| 858 | result = results[D]; | ||
| 859 | break; | ||
| 860 | } | ||
| 861 | case 1: { | 771 | case 1: { |
| 862 | C = 28; | 772 | C = 28; |
| 863 | break; | 773 | break; |
| 864 | } | 774 | } |
| 865 | case 2: { | 775 | case 2: { |
| 866 | C = 13; | 776 | C = 13; |
| 867 | uint b = (bitval >> 1) & 1; | 777 | const uint b = (bitval >> 1) & 1; |
| 868 | B = (b << 6) | (b << 1); | 778 | B = (b << 6) | (b << 1); |
| 869 | break; | 779 | break; |
| 870 | } | 780 | } |
| @@ -872,7 +782,7 @@ uint UnquantizeTexelWeight(EncodingData val) { | |||
| 872 | break; | 782 | break; |
| 873 | } | 783 | } |
| 874 | } | 784 | } |
| 875 | if (val.encoding != JUST_BITS && bitlen > 0) { | 785 | if (encoding != JUST_BITS && bitlen > 0) { |
| 876 | result = D * C + B; | 786 | result = D * C + B; |
| 877 | result ^= A; | 787 | result ^= A; |
| 878 | result = (A & 0x20) | (result >> 2); | 788 | result = (A & 0x20) | (result >> 2); |
| @@ -883,61 +793,77 @@ uint UnquantizeTexelWeight(EncodingData val) { | |||
| 883 | return result; | 793 | return result; |
| 884 | } | 794 | } |
| 885 | 795 | ||
| 886 | void UnquantizeTexelWeights(bool dual_plane, uvec2 size) { | 796 | void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) { |
| 887 | uint weight_idx = 0; | 797 | const uint num_planes = is_dual_plane ? 2 : 1; |
| 888 | uint unquantized[2][144]; | 798 | const uint area = size.x * size.y; |
| 889 | uint area = size.x * size.y; | 799 | const uint loop_count = min(result_index, area * num_planes); |
| 890 | for (uint itr = 0; itr < texel_vector_index; itr++) { | 800 | for (uint itr = 0; itr < loop_count; ++itr) { |
| 891 | unquantized[0][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]); | 801 | result_vector[itr] = |
| 892 | if (dual_plane) { | 802 | UnquantizeTexelWeight(GetEncodingFromVector(itr)); |
| 893 | ++itr; | ||
| 894 | unquantized[1][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]); | ||
| 895 | if (itr == texel_vector_index) { | ||
| 896 | break; | ||
| 897 | } | ||
| 898 | } | ||
| 899 | if (++weight_idx >= (area)) | ||
| 900 | break; | ||
| 901 | } | 803 | } |
| 804 | } | ||
| 805 | |||
| 806 | uint GetUnquantizedTexelWieght(uint offset_base, uint plane, bool is_dual_plane) { | ||
| 807 | const uint offset = is_dual_plane ? 2 * offset_base + plane : offset_base; | ||
| 808 | return result_vector[offset]; | ||
| 809 | } | ||
| 902 | 810 | ||
| 811 | uvec4 GetUnquantizedWeightVector(uint t, uint s, uvec2 size, uint plane_index, bool is_dual_plane) { | ||
| 903 | const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1)); | 812 | const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1)); |
| 904 | const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1)); | 813 | const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1)); |
| 905 | const uint k_plane_scale = dual_plane ? 2 : 1; | 814 | const uint area = size.x * size.y; |
| 906 | for (uint plane = 0; plane < k_plane_scale; plane++) { | 815 | |
| 907 | for (uint t = 0; t < block_dims.y; t++) { | 816 | const uint cs = Ds * s; |
| 908 | for (uint s = 0; s < block_dims.x; s++) { | 817 | const uint ct = Dt * t; |
| 909 | uint cs = Ds * s; | 818 | const uint gs = (cs * (size.x - 1) + 32) >> 6; |
| 910 | uint ct = Dt * t; | 819 | const uint gt = (ct * (size.y - 1) + 32) >> 6; |
| 911 | uint gs = (cs * (size.x - 1) + 32) >> 6; | 820 | const uint js = gs >> 4; |
| 912 | uint gt = (ct * (size.y - 1) + 32) >> 6; | 821 | const uint fs = gs & 0xF; |
| 913 | uint js = gs >> 4; | 822 | const uint jt = gt >> 4; |
| 914 | uint fs = gs & 0xF; | 823 | const uint ft = gt & 0x0F; |
| 915 | uint jt = gt >> 4; | 824 | const uint w11 = (fs * ft + 8) >> 4; |
| 916 | uint ft = gt & 0x0F; | 825 | const uint w10 = ft - w11; |
| 917 | uint w11 = (fs * ft + 8) >> 4; | 826 | const uint w01 = fs - w11; |
| 918 | uint w10 = ft - w11; | 827 | const uint w00 = 16 - fs - ft + w11; |
| 919 | uint w01 = fs - w11; | 828 | const uvec4 w = uvec4(w00, w01, w10, w11); |
| 920 | uint w00 = 16 - fs - ft + w11; | 829 | const uint v0 = jt * size.x + js; |
| 921 | uvec4 w = uvec4(w00, w01, w10, w11); | 830 | |
| 922 | uint v0 = jt * size.x + js; | 831 | uvec4 p0 = uvec4(0); |
| 923 | 832 | uvec4 p1 = uvec4(0); | |
| 924 | uvec4 p = uvec4(0); | 833 | |
| 925 | if (v0 < area) { | 834 | if (v0 < area) { |
| 926 | p.x = unquantized[plane][v0]; | 835 | const uint offset_base = v0; |
| 927 | } | 836 | p0.x = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); |
| 928 | if ((v0 + 1) < (area)) { | 837 | p1.x = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); |
| 929 | p.y = unquantized[plane][v0 + 1]; | 838 | } |
| 930 | } | 839 | if ((v0 + 1) < (area)) { |
| 931 | if ((v0 + size.x) < (area)) { | 840 | const uint offset_base = v0 + 1; |
| 932 | p.z = unquantized[plane][(v0 + size.x)]; | 841 | p0.y = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); |
| 933 | } | 842 | p1.y = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); |
| 934 | if ((v0 + size.x + 1) < (area)) { | 843 | } |
| 935 | p.w = unquantized[plane][(v0 + size.x + 1)]; | 844 | if ((v0 + size.x) < (area)) { |
| 936 | } | 845 | const uint offset_base = v0 + size.x; |
| 937 | unquantized_texel_weights[plane][t * block_dims.x + s] = (uint(dot(p, w)) + 8) >> 4; | 846 | p0.z = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); |
| 938 | } | 847 | p1.z = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); |
| 848 | } | ||
| 849 | if ((v0 + size.x + 1) < (area)) { | ||
| 850 | const uint offset_base = v0 + size.x + 1; | ||
| 851 | p0.w = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); | ||
| 852 | p1.w = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); | ||
| 853 | } | ||
| 854 | |||
| 855 | const uint primary_weight = (uint(dot(p0, w)) + 8) >> 4; | ||
| 856 | |||
| 857 | uvec4 weight_vec = uvec4(primary_weight); | ||
| 858 | |||
| 859 | if (is_dual_plane) { | ||
| 860 | const uint secondary_weight = (uint(dot(p1, w)) + 8) >> 4; | ||
| 861 | for (uint c = 0; c < 4; c++) { | ||
| 862 | const bool is_secondary = ((plane_index + 1u) & 3u) == c; | ||
| 863 | weight_vec[c] = is_secondary ? secondary_weight : primary_weight; | ||
| 939 | } | 864 | } |
| 940 | } | 865 | } |
| 866 | return weight_vec; | ||
| 941 | } | 867 | } |
| 942 | 868 | ||
| 943 | int FindLayout(uint mode) { | 869 | int FindLayout(uint mode) { |
| @@ -971,80 +897,96 @@ int FindLayout(uint mode) { | |||
| 971 | return 5; | 897 | return 5; |
| 972 | } | 898 | } |
| 973 | 899 | ||
| 974 | TexelWeightParams DecodeBlockInfo() { | 900 | |
| 975 | TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false); | 901 | void FillError(ivec3 coord) { |
| 976 | uint mode = StreamBits(11); | 902 | for (uint j = 0; j < block_dims.y; j++) { |
| 903 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 904 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0)); | ||
| 905 | } | ||
| 906 | } | ||
| 907 | } | ||
| 908 | |||
| 909 | void FillVoidExtentLDR(ivec3 coord) { | ||
| 910 | SkipBits(52); | ||
| 911 | const uint r_u = StreamBits(16); | ||
| 912 | const uint g_u = StreamBits(16); | ||
| 913 | const uint b_u = StreamBits(16); | ||
| 914 | const uint a_u = StreamBits(16); | ||
| 915 | const float a = float(a_u) / 65535.0f; | ||
| 916 | const float r = float(r_u) / 65535.0f; | ||
| 917 | const float g = float(g_u) / 65535.0f; | ||
| 918 | const float b = float(b_u) / 65535.0f; | ||
| 919 | for (uint j = 0; j < block_dims.y; j++) { | ||
| 920 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 921 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a)); | ||
| 922 | } | ||
| 923 | } | ||
| 924 | } | ||
| 925 | |||
| 926 | bool IsError(uint mode) { | ||
| 977 | if ((mode & 0x1ff) == 0x1fc) { | 927 | if ((mode & 0x1ff) == 0x1fc) { |
| 978 | if ((mode & 0x200) != 0) { | 928 | if ((mode & 0x200) != 0) { |
| 979 | params.void_extent_hdr = true; | 929 | // params.void_extent_hdr = true; |
| 980 | } else { | 930 | return true; |
| 981 | params.void_extent_ldr = true; | ||
| 982 | } | 931 | } |
| 983 | if ((mode & 0x400) == 0 || StreamBits(1) == 0) { | 932 | if ((mode & 0x400) == 0 || StreamBits(1) == 0) { |
| 984 | params.error_state = true; | 933 | return true; |
| 985 | } | 934 | } |
| 986 | return params; | 935 | return false; |
| 987 | } | 936 | } |
| 988 | if ((mode & 0xf) == 0) { | 937 | if ((mode & 0xf) == 0) { |
| 989 | params.error_state = true; | 938 | return true; |
| 990 | return params; | ||
| 991 | } | 939 | } |
| 992 | if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) { | 940 | if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) { |
| 993 | params.error_state = true; | 941 | return true; |
| 994 | return params; | ||
| 995 | } | 942 | } |
| 943 | return false; | ||
| 944 | } | ||
| 945 | |||
| 946 | uvec2 DecodeBlockSize(uint mode) { | ||
| 996 | uint A, B; | 947 | uint A, B; |
| 997 | uint mode_layout = FindLayout(mode); | 948 | switch (FindLayout(mode)) { |
| 998 | switch (mode_layout) { | ||
| 999 | case 0: | 949 | case 0: |
| 1000 | A = (mode >> 5) & 0x3; | 950 | A = (mode >> 5) & 0x3; |
| 1001 | B = (mode >> 7) & 0x3; | 951 | B = (mode >> 7) & 0x3; |
| 1002 | params.size = uvec2(B + 4, A + 2); | 952 | return uvec2(B + 4, A + 2); |
| 1003 | break; | ||
| 1004 | case 1: | 953 | case 1: |
| 1005 | A = (mode >> 5) & 0x3; | 954 | A = (mode >> 5) & 0x3; |
| 1006 | B = (mode >> 7) & 0x3; | 955 | B = (mode >> 7) & 0x3; |
| 1007 | params.size = uvec2(B + 8, A + 2); | 956 | return uvec2(B + 8, A + 2); |
| 1008 | break; | ||
| 1009 | case 2: | 957 | case 2: |
| 1010 | A = (mode >> 5) & 0x3; | 958 | A = (mode >> 5) & 0x3; |
| 1011 | B = (mode >> 7) & 0x3; | 959 | B = (mode >> 7) & 0x3; |
| 1012 | params.size = uvec2(A + 2, B + 8); | 960 | return uvec2(A + 2, B + 8); |
| 1013 | break; | ||
| 1014 | case 3: | 961 | case 3: |
| 1015 | A = (mode >> 5) & 0x3; | 962 | A = (mode >> 5) & 0x3; |
| 1016 | B = (mode >> 7) & 0x1; | 963 | B = (mode >> 7) & 0x1; |
| 1017 | params.size = uvec2(A + 2, B + 6); | 964 | return uvec2(A + 2, B + 6); |
| 1018 | break; | ||
| 1019 | case 4: | 965 | case 4: |
| 1020 | A = (mode >> 5) & 0x3; | 966 | A = (mode >> 5) & 0x3; |
| 1021 | B = (mode >> 7) & 0x1; | 967 | B = (mode >> 7) & 0x1; |
| 1022 | params.size = uvec2(B + 2, A + 2); | 968 | return uvec2(B + 2, A + 2); |
| 1023 | break; | ||
| 1024 | case 5: | 969 | case 5: |
| 1025 | A = (mode >> 5) & 0x3; | 970 | A = (mode >> 5) & 0x3; |
| 1026 | params.size = uvec2(12, A + 2); | 971 | return uvec2(12, A + 2); |
| 1027 | break; | ||
| 1028 | case 6: | 972 | case 6: |
| 1029 | A = (mode >> 5) & 0x3; | 973 | A = (mode >> 5) & 0x3; |
| 1030 | params.size = uvec2(A + 2, 12); | 974 | return uvec2(A + 2, 12); |
| 1031 | break; | ||
| 1032 | case 7: | 975 | case 7: |
| 1033 | params.size = uvec2(6, 10); | 976 | return uvec2(6, 10); |
| 1034 | break; | ||
| 1035 | case 8: | 977 | case 8: |
| 1036 | params.size = uvec2(10, 6); | 978 | return uvec2(10, 6); |
| 1037 | break; | ||
| 1038 | case 9: | 979 | case 9: |
| 1039 | A = (mode >> 5) & 0x3; | 980 | A = (mode >> 5) & 0x3; |
| 1040 | B = (mode >> 9) & 0x3; | 981 | B = (mode >> 9) & 0x3; |
| 1041 | params.size = uvec2(A + 6, B + 6); | 982 | return uvec2(A + 6, B + 6); |
| 1042 | break; | ||
| 1043 | default: | 983 | default: |
| 1044 | params.error_state = true; | 984 | return uvec2(0); |
| 1045 | break; | ||
| 1046 | } | 985 | } |
| 1047 | params.dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0); | 986 | } |
| 987 | |||
| 988 | uint DecodeMaxWeight(uint mode) { | ||
| 989 | const uint mode_layout = FindLayout(mode); | ||
| 1048 | uint weight_index = (mode & 0x10) != 0 ? 1 : 0; | 990 | uint weight_index = (mode & 0x10) != 0 ? 1 : 0; |
| 1049 | if (mode_layout < 5) { | 991 | if (mode_layout < 5) { |
| 1050 | weight_index |= (mode & 0x3) << 1; | 992 | weight_index |= (mode & 0x3) << 1; |
| @@ -1053,64 +995,34 @@ TexelWeightParams DecodeBlockInfo() { | |||
| 1053 | } | 995 | } |
| 1054 | weight_index -= 2; | 996 | weight_index -= 2; |
| 1055 | if ((mode_layout != 9) && ((mode & 0x200) != 0)) { | 997 | if ((mode_layout != 9) && ((mode & 0x200) != 0)) { |
| 1056 | const int max_weights[6] = int[6](7, 8, 9, 10, 11, 12); | 998 | weight_index += 6; |
| 1057 | params.max_weight = max_weights[weight_index]; | ||
| 1058 | } else { | ||
| 1059 | const int max_weights[6] = int[6](1, 2, 3, 4, 5, 6); | ||
| 1060 | params.max_weight = max_weights[weight_index]; | ||
| 1061 | } | ||
| 1062 | return params; | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | void FillError(ivec3 coord) { | ||
| 1066 | for (uint j = 0; j < block_dims.y; j++) { | ||
| 1067 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 1068 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0)); | ||
| 1069 | } | ||
| 1070 | } | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | void FillVoidExtentLDR(ivec3 coord) { | ||
| 1074 | StreamBits(52); | ||
| 1075 | uint r_u = StreamBits(16); | ||
| 1076 | uint g_u = StreamBits(16); | ||
| 1077 | uint b_u = StreamBits(16); | ||
| 1078 | uint a_u = StreamBits(16); | ||
| 1079 | float a = float(a_u) / 65535.0f; | ||
| 1080 | float r = float(r_u) / 65535.0f; | ||
| 1081 | float g = float(g_u) / 65535.0f; | ||
| 1082 | float b = float(b_u) / 65535.0f; | ||
| 1083 | for (uint j = 0; j < block_dims.y; j++) { | ||
| 1084 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 1085 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a)); | ||
| 1086 | } | ||
| 1087 | } | 999 | } |
| 1000 | return weight_index + 1; | ||
| 1088 | } | 1001 | } |
| 1089 | 1002 | ||
| 1090 | void DecompressBlock(ivec3 coord) { | 1003 | void DecompressBlock(ivec3 coord) { |
| 1091 | TexelWeightParams params = DecodeBlockInfo(); | 1004 | uint mode = StreamBits(11); |
| 1092 | if (params.error_state) { | 1005 | if (IsError(mode)) { |
| 1093 | FillError(coord); | ||
| 1094 | return; | ||
| 1095 | } | ||
| 1096 | if (params.void_extent_hdr) { | ||
| 1097 | FillError(coord); | 1006 | FillError(coord); |
| 1098 | return; | 1007 | return; |
| 1099 | } | 1008 | } |
| 1100 | if (params.void_extent_ldr) { | 1009 | if ((mode & 0x1ff) == 0x1fc) { |
| 1010 | // params.void_extent_ldr = true; | ||
| 1101 | FillVoidExtentLDR(coord); | 1011 | FillVoidExtentLDR(coord); |
| 1102 | return; | 1012 | return; |
| 1103 | } | 1013 | } |
| 1104 | if ((params.size.x > block_dims.x) || (params.size.y > block_dims.y)) { | 1014 | const uvec2 size_params = DecodeBlockSize(mode); |
| 1015 | if ((size_params.x > block_dims.x) || (size_params.y > block_dims.y)) { | ||
| 1105 | FillError(coord); | 1016 | FillError(coord); |
| 1106 | return; | 1017 | return; |
| 1107 | } | 1018 | } |
| 1108 | uint num_partitions = StreamBits(2) + 1; | 1019 | const uint num_partitions = StreamBits(2) + 1; |
| 1109 | if (num_partitions > 4 || (num_partitions == 4 && params.dual_plane)) { | 1020 | const uint mode_layout = FindLayout(mode); |
| 1021 | const bool dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0); | ||
| 1022 | if (num_partitions > 4 || (num_partitions == 4 && dual_plane)) { | ||
| 1110 | FillError(coord); | 1023 | FillError(coord); |
| 1111 | return; | 1024 | return; |
| 1112 | } | 1025 | } |
| 1113 | int plane_index = -1; | ||
| 1114 | uint partition_index = 1; | 1026 | uint partition_index = 1; |
| 1115 | uvec4 color_endpoint_mode = uvec4(0); | 1027 | uvec4 color_endpoint_mode = uvec4(0); |
| 1116 | uint ced_pointer = 0; | 1028 | uint ced_pointer = 0; |
| @@ -1122,8 +1034,9 @@ void DecompressBlock(ivec3 coord) { | |||
| 1122 | partition_index = StreamBits(10); | 1034 | partition_index = StreamBits(10); |
| 1123 | base_cem = StreamBits(6); | 1035 | base_cem = StreamBits(6); |
| 1124 | } | 1036 | } |
| 1125 | uint base_mode = base_cem & 3; | 1037 | const uint base_mode = base_cem & 3; |
| 1126 | uint weight_bits = GetPackedBitSize(params.size, params.dual_plane, params.max_weight); | 1038 | const uint max_weight = DecodeMaxWeight(mode); |
| 1039 | const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight); | ||
| 1127 | uint remaining_bits = 128 - weight_bits - total_bitsread; | 1040 | uint remaining_bits = 128 - weight_bits - total_bitsread; |
| 1128 | uint extra_cem_bits = 0; | 1041 | uint extra_cem_bits = 0; |
| 1129 | if (base_mode > 0) { | 1042 | if (base_mode > 0) { |
| @@ -1142,10 +1055,7 @@ void DecompressBlock(ivec3 coord) { | |||
| 1142 | } | 1055 | } |
| 1143 | } | 1056 | } |
| 1144 | remaining_bits -= extra_cem_bits; | 1057 | remaining_bits -= extra_cem_bits; |
| 1145 | uint plane_selector_bits = 0; | 1058 | const uint plane_selector_bits = dual_plane ? 2 : 0; |
| 1146 | if (params.dual_plane) { | ||
| 1147 | plane_selector_bits = 2; | ||
| 1148 | } | ||
| 1149 | remaining_bits -= plane_selector_bits; | 1059 | remaining_bits -= plane_selector_bits; |
| 1150 | if (remaining_bits > 128) { | 1060 | if (remaining_bits > 128) { |
| 1151 | // Bad data, more remaining bits than 4 bytes | 1061 | // Bad data, more remaining bits than 4 bytes |
| @@ -1153,17 +1063,17 @@ void DecompressBlock(ivec3 coord) { | |||
| 1153 | return; | 1063 | return; |
| 1154 | } | 1064 | } |
| 1155 | // Read color data... | 1065 | // Read color data... |
| 1156 | uint color_data_bits = remaining_bits; | 1066 | const uint color_data_bits = remaining_bits; |
| 1157 | while (remaining_bits > 0) { | 1067 | while (remaining_bits > 0) { |
| 1158 | int nb = int(min(remaining_bits, 32U)); | 1068 | const int nb = int(min(remaining_bits, 32U)); |
| 1159 | uint b = StreamBits(nb); | 1069 | const uint b = StreamBits(nb); |
| 1160 | color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); | 1070 | color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); |
| 1161 | ++ced_pointer; | 1071 | ++ced_pointer; |
| 1162 | remaining_bits -= nb; | 1072 | remaining_bits -= nb; |
| 1163 | } | 1073 | } |
| 1164 | plane_index = int(StreamBits(plane_selector_bits)); | 1074 | const uint plane_index = uint(StreamBits(plane_selector_bits)); |
| 1165 | if (base_mode > 0) { | 1075 | if (base_mode > 0) { |
| 1166 | uint extra_cem = StreamBits(extra_cem_bits); | 1076 | const uint extra_cem = StreamBits(extra_cem_bits); |
| 1167 | uint cem = (extra_cem << 6) | base_cem; | 1077 | uint cem = (extra_cem << 6) | base_cem; |
| 1168 | cem >>= 2; | 1078 | cem >>= 2; |
| 1169 | uvec4 C = uvec4(0); | 1079 | uvec4 C = uvec4(0); |
| @@ -1185,70 +1095,80 @@ void DecompressBlock(ivec3 coord) { | |||
| 1185 | color_endpoint_mode[i] |= M[i]; | 1095 | color_endpoint_mode[i] |= M[i]; |
| 1186 | } | 1096 | } |
| 1187 | } else if (num_partitions > 1) { | 1097 | } else if (num_partitions > 1) { |
| 1188 | uint cem = base_cem >> 2; | 1098 | const uint cem = base_cem >> 2; |
| 1189 | for (uint i = 0; i < num_partitions; i++) { | 1099 | for (uint i = 0; i < num_partitions; i++) { |
| 1190 | color_endpoint_mode[i] = cem; | 1100 | color_endpoint_mode[i] = cem; |
| 1191 | } | 1101 | } |
| 1192 | } | 1102 | } |
| 1193 | DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits); | ||
| 1194 | 1103 | ||
| 1195 | uvec4 endpoints[4][2]; | 1104 | uvec4 endpoints0[4]; |
| 1196 | for (uint i = 0; i < num_partitions; i++) { | 1105 | uvec4 endpoints1[4]; |
| 1197 | ComputeEndpoints(endpoints[i][0], endpoints[i][1], color_endpoint_mode[i]); | 1106 | { |
| 1107 | // This decode phase should at most push 32 elements into the vector | ||
| 1108 | result_vector_max_index = 32; | ||
| 1109 | uint color_values[32]; | ||
| 1110 | uint colvals_index = 0; | ||
| 1111 | DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits, color_values); | ||
| 1112 | for (uint i = 0; i < num_partitions; i++) { | ||
| 1113 | ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values, | ||
| 1114 | colvals_index); | ||
| 1115 | } | ||
| 1198 | } | 1116 | } |
| 1117 | color_endpoint_data = local_buff; | ||
| 1118 | color_endpoint_data = bitfieldReverse(color_endpoint_data).wzyx; | ||
| 1119 | const uint clear_byte_start = (weight_bits >> 3) + 1; | ||
| 1199 | 1120 | ||
| 1200 | texel_weight_data = local_buff; | 1121 | const uint byte_insert = ExtractBits(color_endpoint_data, int(clear_byte_start - 1) * 8, 8) & |
| 1201 | texel_weight_data = bitfieldReverse(texel_weight_data).wzyx; | 1122 | uint(((1 << (weight_bits % 8)) - 1)); |
| 1202 | uint clear_byte_start = | 1123 | const uint vec_index = (clear_byte_start - 1) >> 2; |
| 1203 | (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) >> 3) + 1; | 1124 | color_endpoint_data[vec_index] = bitfieldInsert(color_endpoint_data[vec_index], byte_insert, |
| 1204 | 1125 | int((clear_byte_start - 1) % 4) * 8, 8); | |
| 1205 | uint byte_insert = ExtractBits(texel_weight_data, int(clear_byte_start - 1) * 8, 8) & | ||
| 1206 | uint( | ||
| 1207 | ((1 << (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) % 8)) - 1)); | ||
| 1208 | uint vec_index = (clear_byte_start - 1) >> 2; | ||
| 1209 | texel_weight_data[vec_index] = | ||
| 1210 | bitfieldInsert(texel_weight_data[vec_index], byte_insert, int((clear_byte_start - 1) % 4) * 8, 8); | ||
| 1211 | for (uint i = clear_byte_start; i < 16; ++i) { | 1126 | for (uint i = clear_byte_start; i < 16; ++i) { |
| 1212 | uint idx = i >> 2; | 1127 | const uint idx = i >> 2; |
| 1213 | texel_weight_data[idx] = bitfieldInsert(texel_weight_data[idx], 0, int(i % 4) * 8, 8); | 1128 | color_endpoint_data[idx] = bitfieldInsert(color_endpoint_data[idx], 0, int(i % 4) * 8, 8); |
| 1214 | } | 1129 | } |
| 1215 | texel_flag = true; // use texel "vector" and bit stream in integer decoding | ||
| 1216 | DecodeIntegerSequence(params.max_weight, GetNumWeightValues(params.size, params.dual_plane)); | ||
| 1217 | 1130 | ||
| 1218 | UnquantizeTexelWeights(params.dual_plane, params.size); | 1131 | // Re-init vector variables for next decode phase |
| 1132 | result_index = 0; | ||
| 1133 | color_bitsread = 0; | ||
| 1134 | result_limit_reached = false; | ||
| 1219 | 1135 | ||
| 1136 | // The limit for the Unquantize phase, avoids decoding more data than needed. | ||
| 1137 | result_vector_max_index = size_params.x * size_params.y; | ||
| 1138 | if (dual_plane) { | ||
| 1139 | result_vector_max_index *= 2; | ||
| 1140 | } | ||
| 1141 | DecodeIntegerSequence(max_weight, GetNumWeightValues(size_params, dual_plane)); | ||
| 1142 | |||
| 1143 | UnquantizeTexelWeights(size_params, dual_plane); | ||
| 1220 | for (uint j = 0; j < block_dims.y; j++) { | 1144 | for (uint j = 0; j < block_dims.y; j++) { |
| 1221 | for (uint i = 0; i < block_dims.x; i++) { | 1145 | for (uint i = 0; i < block_dims.x; i++) { |
| 1222 | uint local_partition = 0; | 1146 | uint local_partition = 0; |
| 1223 | if (num_partitions > 1) { | 1147 | if (num_partitions > 1) { |
| 1224 | local_partition = Select2DPartition(partition_index, i, j, num_partitions, | 1148 | local_partition = Select2DPartition(partition_index, i, j, num_partitions); |
| 1225 | (block_dims.y * block_dims.x) < 32); | ||
| 1226 | } | ||
| 1227 | vec4 p; | ||
| 1228 | uvec4 C0 = ReplicateByteTo16(endpoints[local_partition][0]); | ||
| 1229 | uvec4 C1 = ReplicateByteTo16(endpoints[local_partition][1]); | ||
| 1230 | uvec4 plane_vec = uvec4(0); | ||
| 1231 | uvec4 weight_vec = uvec4(0); | ||
| 1232 | for (uint c = 0; c < 4; c++) { | ||
| 1233 | if (params.dual_plane && (((plane_index + 1) & 3) == c)) { | ||
| 1234 | plane_vec[c] = 1; | ||
| 1235 | } | ||
| 1236 | weight_vec[c] = unquantized_texel_weights[plane_vec[c]][j * block_dims.x + i]; | ||
| 1237 | } | 1149 | } |
| 1238 | vec4 Cf = vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); | 1150 | const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); |
| 1239 | p = (Cf / 65535.0); | 1151 | const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); |
| 1152 | const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane); | ||
| 1153 | const vec4 Cf = | ||
| 1154 | vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); | ||
| 1155 | const vec4 p = (Cf / 65535.0f); | ||
| 1240 | imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); | 1156 | imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); |
| 1241 | } | 1157 | } |
| 1242 | } | 1158 | } |
| 1243 | } | 1159 | } |
| 1244 | 1160 | ||
| 1161 | uint SwizzleOffset(uvec2 pos) { | ||
| 1162 | const uint x = pos.x; | ||
| 1163 | const uint y = pos.y; | ||
| 1164 | return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + | ||
| 1165 | ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16); | ||
| 1166 | } | ||
| 1167 | |||
| 1245 | void main() { | 1168 | void main() { |
| 1246 | uvec3 pos = gl_GlobalInvocationID; | 1169 | uvec3 pos = gl_GlobalInvocationID; |
| 1247 | pos.x <<= BYTES_PER_BLOCK_LOG2; | 1170 | pos.x <<= BYTES_PER_BLOCK_LOG2; |
| 1248 | |||
| 1249 | // Read as soon as possible due to its latency | ||
| 1250 | const uint swizzle = SwizzleOffset(pos.xy); | 1171 | const uint swizzle = SwizzleOffset(pos.xy); |
| 1251 | |||
| 1252 | const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; | 1172 | const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; |
| 1253 | 1173 | ||
| 1254 | uint offset = 0; | 1174 | uint offset = 0; |
| @@ -1262,8 +1182,6 @@ void main() { | |||
| 1262 | if (any(greaterThanEqual(coord, imageSize(dest_image)))) { | 1182 | if (any(greaterThanEqual(coord, imageSize(dest_image)))) { |
| 1263 | return; | 1183 | return; |
| 1264 | } | 1184 | } |
| 1265 | current_index = 0; | ||
| 1266 | bitsread = 0; | ||
| 1267 | local_buff = astc_data[offset / 16]; | 1185 | local_buff = astc_data[offset / 16]; |
| 1268 | DecompressBlock(coord); | 1186 | DecompressBlock(coord); |
| 1269 | } | 1187 | } |
diff --git a/src/video_core/host_shaders/vulkan_depthstencil_clear.frag b/src/video_core/host_shaders/vulkan_depthstencil_clear.frag new file mode 100644 index 000000000..1ac177c7e --- /dev/null +++ b/src/video_core/host_shaders/vulkan_depthstencil_clear.frag | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #version 460 core | ||
| 5 | |||
| 6 | layout (push_constant) uniform PushConstants { | ||
| 7 | vec4 clear_depth; | ||
| 8 | }; | ||
| 9 | |||
| 10 | void main() { | ||
| 11 | gl_FragDepth = clear_depth.x; | ||
| 12 | } | ||
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp index 905505ca1..5d0bb9cc4 100644 --- a/src/video_core/macro/macro.cpp +++ b/src/video_core/macro/macro.cpp | |||
| @@ -27,14 +27,24 @@ MICROPROFILE_DEFINE(MacroHLE, "GPU", "Execute macro HLE", MP_RGB(128, 192, 192)) | |||
| 27 | 27 | ||
| 28 | namespace Tegra { | 28 | namespace Tegra { |
| 29 | 29 | ||
| 30 | static void Dump(u64 hash, std::span<const u32> code) { | 30 | static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) { |
| 31 | const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)}; | 31 | const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)}; |
| 32 | const auto macro_dir{base_dir / "macros"}; | 32 | const auto macro_dir{base_dir / "macros"}; |
| 33 | if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { | 33 | if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { |
| 34 | LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); | 34 | LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); |
| 35 | return; | 35 | return; |
| 36 | } | 36 | } |
| 37 | const auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; | 37 | auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; |
| 38 | |||
| 39 | if (decompiled) { | ||
| 40 | auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)}; | ||
| 41 | if (Common::FS::Exists(name)) { | ||
| 42 | (void)Common::FS::RenameFile(name, new_name); | ||
| 43 | return; | ||
| 44 | } | ||
| 45 | name = new_name; | ||
| 46 | } | ||
| 47 | |||
| 38 | std::fstream macro_file(name, std::ios::out | std::ios::binary); | 48 | std::fstream macro_file(name, std::ios::out | std::ios::binary); |
| 39 | if (!macro_file) { | 49 | if (!macro_file) { |
| 40 | LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}", | 50 | LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}", |
| @@ -90,9 +100,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { | |||
| 90 | if (!mid_method.has_value()) { | 100 | if (!mid_method.has_value()) { |
| 91 | cache_info.lle_program = Compile(macro_code->second); | 101 | cache_info.lle_program = Compile(macro_code->second); |
| 92 | cache_info.hash = Common::HashValue(macro_code->second); | 102 | cache_info.hash = Common::HashValue(macro_code->second); |
| 93 | if (Settings::values.dump_macros) { | ||
| 94 | Dump(cache_info.hash, macro_code->second); | ||
| 95 | } | ||
| 96 | } else { | 103 | } else { |
| 97 | const auto& macro_cached = uploaded_macro_code[mid_method.value()]; | 104 | const auto& macro_cached = uploaded_macro_code[mid_method.value()]; |
| 98 | const auto rebased_method = method - mid_method.value(); | 105 | const auto rebased_method = method - mid_method.value(); |
| @@ -102,9 +109,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { | |||
| 102 | code.size() * sizeof(u32)); | 109 | code.size() * sizeof(u32)); |
| 103 | cache_info.hash = Common::HashValue(code); | 110 | cache_info.hash = Common::HashValue(code); |
| 104 | cache_info.lle_program = Compile(code); | 111 | cache_info.lle_program = Compile(code); |
| 105 | if (Settings::values.dump_macros) { | ||
| 106 | Dump(cache_info.hash, code); | ||
| 107 | } | ||
| 108 | } | 112 | } |
| 109 | 113 | ||
| 110 | auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); | 114 | auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); |
| @@ -117,6 +121,10 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { | |||
| 117 | MICROPROFILE_SCOPE(MacroHLE); | 121 | MICROPROFILE_SCOPE(MacroHLE); |
| 118 | cache_info.hle_program->Execute(parameters, method); | 122 | cache_info.hle_program->Execute(parameters, method); |
| 119 | } | 123 | } |
| 124 | |||
| 125 | if (Settings::values.dump_macros) { | ||
| 126 | Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program); | ||
| 127 | } | ||
| 120 | } | 128 | } |
| 121 | } | 129 | } |
| 122 | 130 | ||
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index f822fa856..44a771d65 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp | |||
| @@ -220,7 +220,8 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c | |||
| 220 | ASSERT(num_textures <= MAX_TEXTURES); | 220 | ASSERT(num_textures <= MAX_TEXTURES); |
| 221 | ASSERT(num_images <= MAX_IMAGES); | 221 | ASSERT(num_images <= MAX_IMAGES); |
| 222 | 222 | ||
| 223 | const bool assembly_shaders{assembly_programs[0].handle != 0}; | 223 | const auto backend = device.GetShaderBackend(); |
| 224 | const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm}; | ||
| 224 | use_storage_buffers = | 225 | use_storage_buffers = |
| 225 | !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); | 226 | !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); |
| 226 | writes_global_memory &= !use_storage_buffers; | 227 | writes_global_memory &= !use_storage_buffers; |
| @@ -230,7 +231,6 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c | |||
| 230 | GenerateTransformFeedbackState(); | 231 | GenerateTransformFeedbackState(); |
| 231 | } | 232 | } |
| 232 | const bool in_parallel = thread_worker != nullptr; | 233 | const bool in_parallel = thread_worker != nullptr; |
| 233 | const auto backend = device.GetShaderBackend(); | ||
| 234 | auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv), | 234 | auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv), |
| 235 | shader_notify, backend, in_parallel, | 235 | shader_notify, backend, in_parallel, |
| 236 | force_context_flush](ShaderContext::Context*) mutable { | 236 | force_context_flush](ShaderContext::Context*) mutable { |
| @@ -559,15 +559,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 559 | } | 559 | } |
| 560 | 560 | ||
| 561 | void GraphicsPipeline::ConfigureTransformFeedbackImpl() const { | 561 | void GraphicsPipeline::ConfigureTransformFeedbackImpl() const { |
| 562 | glTransformFeedbackStreamAttribsNV(num_xfb_attribs, xfb_attribs.data(), num_xfb_strides, | 562 | glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), GL_SEPARATE_ATTRIBS); |
| 563 | xfb_streams.data(), GL_INTERLEAVED_ATTRIBS); | ||
| 564 | } | 563 | } |
| 565 | 564 | ||
| 566 | void GraphicsPipeline::GenerateTransformFeedbackState() { | 565 | void GraphicsPipeline::GenerateTransformFeedbackState() { |
| 567 | // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal | 566 | // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal |
| 568 | // when this is required. | 567 | // when this is required. |
| 569 | GLint* cursor{xfb_attribs.data()}; | 568 | GLint* cursor{xfb_attribs.data()}; |
| 570 | GLint* current_stream{xfb_streams.data()}; | ||
| 571 | 569 | ||
| 572 | for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) { | 570 | for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) { |
| 573 | const auto& layout = key.xfb_state.layouts[feedback]; | 571 | const auto& layout = key.xfb_state.layouts[feedback]; |
| @@ -575,15 +573,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() { | |||
| 575 | if (layout.varying_count == 0) { | 573 | if (layout.varying_count == 0) { |
| 576 | continue; | 574 | continue; |
| 577 | } | 575 | } |
| 578 | *current_stream = static_cast<GLint>(feedback); | ||
| 579 | if (current_stream != xfb_streams.data()) { | ||
| 580 | // When stepping one stream, push the expected token | ||
| 581 | cursor[0] = GL_NEXT_BUFFER_NV; | ||
| 582 | cursor[1] = 0; | ||
| 583 | cursor[2] = 0; | ||
| 584 | cursor += XFB_ENTRY_STRIDE; | ||
| 585 | } | ||
| 586 | ++current_stream; | ||
| 587 | 576 | ||
| 588 | const auto& locations = key.xfb_state.varyings[feedback]; | 577 | const auto& locations = key.xfb_state.varyings[feedback]; |
| 589 | std::optional<u32> current_index; | 578 | std::optional<u32> current_index; |
| @@ -619,7 +608,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() { | |||
| 619 | } | 608 | } |
| 620 | } | 609 | } |
| 621 | num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE); | 610 | num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE); |
| 622 | num_xfb_strides = static_cast<GLsizei>(current_stream - xfb_streams.data()); | ||
| 623 | } | 611 | } |
| 624 | 612 | ||
| 625 | void GraphicsPipeline::WaitForBuild() { | 613 | void GraphicsPipeline::WaitForBuild() { |
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h index 7b3d7eae8..74fc9cc3d 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h | |||
| @@ -154,9 +154,7 @@ private: | |||
| 154 | 154 | ||
| 155 | static constexpr std::size_t XFB_ENTRY_STRIDE = 3; | 155 | static constexpr std::size_t XFB_ENTRY_STRIDE = 3; |
| 156 | GLsizei num_xfb_attribs{}; | 156 | GLsizei num_xfb_attribs{}; |
| 157 | GLsizei num_xfb_strides{}; | ||
| 158 | std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{}; | 157 | std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{}; |
| 159 | std::array<GLint, Maxwell::NumTransformFeedbackBuffers> xfb_streams{}; | ||
| 160 | 158 | ||
| 161 | std::mutex built_mutex; | 159 | std::mutex built_mutex; |
| 162 | std::condition_variable built_condvar; | 160 | std::condition_variable built_condvar; |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index aadd6967c..dd03efecd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -380,6 +380,17 @@ void RasterizerOpenGL::DispatchCompute() { | |||
| 380 | pipeline->SetEngine(kepler_compute, gpu_memory); | 380 | pipeline->SetEngine(kepler_compute, gpu_memory); |
| 381 | pipeline->Configure(); | 381 | pipeline->Configure(); |
| 382 | const auto& qmd{kepler_compute->launch_description}; | 382 | const auto& qmd{kepler_compute->launch_description}; |
| 383 | auto indirect_address = kepler_compute->GetIndirectComputeAddress(); | ||
| 384 | if (indirect_address) { | ||
| 385 | // DispatchIndirect | ||
| 386 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 387 | const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite; | ||
| 388 | const auto [buffer, offset] = | ||
| 389 | buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); | ||
| 390 | glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->Handle()); | ||
| 391 | glDispatchComputeIndirect(static_cast<GLintptr>(offset)); | ||
| 392 | return; | ||
| 393 | } | ||
| 383 | glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); | 394 | glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); |
| 384 | ++num_queued_commands; | 395 | ++num_queued_commands; |
| 385 | has_written_global_memory |= pipeline->WritesGlobalMemory(); | 396 | has_written_global_memory |= pipeline->WritesGlobalMemory(); |
| @@ -1335,7 +1346,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 1335 | } | 1346 | } |
| 1336 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); | 1347 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); |
| 1337 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | 1348 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; |
| 1338 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; | 1349 | const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing |
| 1350 | : VideoCommon::ObtainBufferOperation::MarkAsWritten; | ||
| 1339 | const auto [buffer, offset] = | 1351 | const auto [buffer, offset] = |
| 1340 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); | 1352 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); |
| 1341 | 1353 | ||
| @@ -1344,8 +1356,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 1344 | const std::span copy_span{©, 1}; | 1356 | const std::span copy_span{©, 1}; |
| 1345 | 1357 | ||
| 1346 | if constexpr (IS_IMAGE_UPLOAD) { | 1358 | if constexpr (IS_IMAGE_UPLOAD) { |
| 1359 | texture_cache.PrepareImage(image_id, true, false); | ||
| 1347 | image->UploadMemory(buffer->Handle(), offset, copy_span); | 1360 | image->UploadMemory(buffer->Handle(), offset, copy_span); |
| 1348 | } else { | 1361 | } else { |
| 1362 | if (offset % BytesPerBlock(image->info.format)) { | ||
| 1363 | return false; | ||
| 1364 | } | ||
| 1349 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, | 1365 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, |
| 1350 | buffer_operand.address, buffer_size); | 1366 | buffer_operand.address, buffer_size); |
| 1351 | } | 1367 | } |
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 544982d18..c437013e6 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp | |||
| @@ -68,6 +68,7 @@ void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map, | |||
| 68 | std::span<const VideoCommon::SwizzleParameters> swizzles) { | 68 | std::span<const VideoCommon::SwizzleParameters> swizzles) { |
| 69 | static constexpr GLuint BINDING_INPUT_BUFFER = 0; | 69 | static constexpr GLuint BINDING_INPUT_BUFFER = 0; |
| 70 | static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; | 70 | static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; |
| 71 | program_manager.LocalMemoryWarmup(); | ||
| 71 | 72 | ||
| 72 | const Extent2D tile_size{ | 73 | const Extent2D tile_size{ |
| 73 | .width = VideoCore::Surface::DefaultBlockWidth(image.info.format), | 74 | .width = VideoCore::Surface::DefaultBlockWidth(image.info.format), |
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index f74ae972e..1032c9d12 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp | |||
| @@ -16,6 +16,7 @@ | |||
| 16 | #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" | 16 | #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" |
| 17 | #include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" | 17 | #include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" |
| 18 | #include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" | 18 | #include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" |
| 19 | #include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h" | ||
| 19 | #include "video_core/renderer_vulkan/blit_image.h" | 20 | #include "video_core/renderer_vulkan/blit_image.h" |
| 20 | #include "video_core/renderer_vulkan/maxwell_to_vk.h" | 21 | #include "video_core/renderer_vulkan/maxwell_to_vk.h" |
| 21 | #include "video_core/renderer_vulkan/vk_scheduler.h" | 22 | #include "video_core/renderer_vulkan/vk_scheduler.h" |
| @@ -428,6 +429,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, | |||
| 428 | blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), | 429 | blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), |
| 429 | clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), | 430 | clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), |
| 430 | clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), | 431 | clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), |
| 432 | clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)), | ||
| 431 | convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), | 433 | convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), |
| 432 | convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), | 434 | convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), |
| 433 | convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), | 435 | convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), |
| @@ -593,6 +595,28 @@ void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_ma | |||
| 593 | scheduler.InvalidateState(); | 595 | scheduler.InvalidateState(); |
| 594 | } | 596 | } |
| 595 | 597 | ||
| 598 | void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, | ||
| 599 | f32 clear_depth, u8 stencil_mask, u32 stencil_ref, | ||
| 600 | u32 stencil_compare_mask, const Region2D& dst_region) { | ||
| 601 | const BlitDepthStencilPipelineKey key{ | ||
| 602 | .renderpass = dst_framebuffer->RenderPass(), | ||
| 603 | .depth_clear = depth_clear, | ||
| 604 | .stencil_mask = stencil_mask, | ||
| 605 | .stencil_compare_mask = stencil_compare_mask, | ||
| 606 | .stencil_ref = stencil_ref, | ||
| 607 | }; | ||
| 608 | const VkPipeline pipeline = FindOrEmplaceClearStencilPipeline(key); | ||
| 609 | const VkPipelineLayout layout = *clear_color_pipeline_layout; | ||
| 610 | scheduler.RequestRenderpass(dst_framebuffer); | ||
| 611 | scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { | ||
| 612 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | ||
| 613 | BindBlitState(cmdbuf, dst_region); | ||
| 614 | cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); | ||
| 615 | cmdbuf.Draw(3, 1, 0, 0); | ||
| 616 | }); | ||
| 617 | scheduler.InvalidateState(); | ||
| 618 | } | ||
| 619 | |||
| 596 | void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, | 620 | void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, |
| 597 | const ImageView& src_image_view) { | 621 | const ImageView& src_image_view) { |
| 598 | const VkPipelineLayout layout = *one_texture_pipeline_layout; | 622 | const VkPipelineLayout layout = *one_texture_pipeline_layout; |
| @@ -820,6 +844,61 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel | |||
| 820 | return *clear_color_pipelines.back(); | 844 | return *clear_color_pipelines.back(); |
| 821 | } | 845 | } |
| 822 | 846 | ||
| 847 | VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( | ||
| 848 | const BlitDepthStencilPipelineKey& key) { | ||
| 849 | const auto it = std::ranges::find(clear_stencil_keys, key); | ||
| 850 | if (it != clear_stencil_keys.end()) { | ||
| 851 | return *clear_stencil_pipelines[std::distance(clear_stencil_keys.begin(), it)]; | ||
| 852 | } | ||
| 853 | clear_stencil_keys.push_back(key); | ||
| 854 | const std::array stages = MakeStages(*clear_color_vert, *clear_stencil_frag); | ||
| 855 | const auto stencil = VkStencilOpState{ | ||
| 856 | .failOp = VK_STENCIL_OP_KEEP, | ||
| 857 | .passOp = VK_STENCIL_OP_REPLACE, | ||
| 858 | .depthFailOp = VK_STENCIL_OP_KEEP, | ||
| 859 | .compareOp = VK_COMPARE_OP_ALWAYS, | ||
| 860 | .compareMask = key.stencil_compare_mask, | ||
| 861 | .writeMask = key.stencil_mask, | ||
| 862 | .reference = key.stencil_ref, | ||
| 863 | }; | ||
| 864 | const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{ | ||
| 865 | .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | ||
| 866 | .pNext = nullptr, | ||
| 867 | .flags = 0, | ||
| 868 | .depthTestEnable = VK_FALSE, | ||
| 869 | .depthWriteEnable = key.depth_clear, | ||
| 870 | .depthCompareOp = VK_COMPARE_OP_ALWAYS, | ||
| 871 | .depthBoundsTestEnable = VK_FALSE, | ||
| 872 | .stencilTestEnable = VK_TRUE, | ||
| 873 | .front = stencil, | ||
| 874 | .back = stencil, | ||
| 875 | .minDepthBounds = 0.0f, | ||
| 876 | .maxDepthBounds = 0.0f, | ||
| 877 | }; | ||
| 878 | clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ | ||
| 879 | .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, | ||
| 880 | .pNext = nullptr, | ||
| 881 | .flags = 0, | ||
| 882 | .stageCount = static_cast<u32>(stages.size()), | ||
| 883 | .pStages = stages.data(), | ||
| 884 | .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, | ||
| 885 | .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, | ||
| 886 | .pTessellationState = nullptr, | ||
| 887 | .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, | ||
| 888 | .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, | ||
| 889 | .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, | ||
| 890 | .pDepthStencilState = &depth_stencil_ci, | ||
| 891 | .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, | ||
| 892 | .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, | ||
| 893 | .layout = *clear_color_pipeline_layout, | ||
| 894 | .renderPass = key.renderpass, | ||
| 895 | .subpass = 0, | ||
| 896 | .basePipelineHandle = VK_NULL_HANDLE, | ||
| 897 | .basePipelineIndex = 0, | ||
| 898 | })); | ||
| 899 | return *clear_stencil_pipelines.back(); | ||
| 900 | } | ||
| 901 | |||
| 823 | void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, | 902 | void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, |
| 824 | bool is_target_depth) { | 903 | bool is_target_depth) { |
| 825 | if (pipeline) { | 904 | if (pipeline) { |
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 2976a7d91..dcfe217aa 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h | |||
| @@ -27,6 +27,16 @@ struct BlitImagePipelineKey { | |||
| 27 | Tegra::Engines::Fermi2D::Operation operation; | 27 | Tegra::Engines::Fermi2D::Operation operation; |
| 28 | }; | 28 | }; |
| 29 | 29 | ||
| 30 | struct BlitDepthStencilPipelineKey { | ||
| 31 | constexpr auto operator<=>(const BlitDepthStencilPipelineKey&) const noexcept = default; | ||
| 32 | |||
| 33 | VkRenderPass renderpass; | ||
| 34 | bool depth_clear; | ||
| 35 | u8 stencil_mask; | ||
| 36 | u32 stencil_compare_mask; | ||
| 37 | u32 stencil_ref; | ||
| 38 | }; | ||
| 39 | |||
| 30 | class BlitImageHelper { | 40 | class BlitImageHelper { |
| 31 | public: | 41 | public: |
| 32 | explicit BlitImageHelper(const Device& device, Scheduler& scheduler, | 42 | explicit BlitImageHelper(const Device& device, Scheduler& scheduler, |
| @@ -64,6 +74,10 @@ public: | |||
| 64 | void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, | 74 | void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, |
| 65 | const std::array<f32, 4>& clear_color, const Region2D& dst_region); | 75 | const std::array<f32, 4>& clear_color, const Region2D& dst_region); |
| 66 | 76 | ||
| 77 | void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth, | ||
| 78 | u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask, | ||
| 79 | const Region2D& dst_region); | ||
| 80 | |||
| 67 | private: | 81 | private: |
| 68 | void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, | 82 | void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, |
| 69 | const ImageView& src_image_view); | 83 | const ImageView& src_image_view); |
| @@ -76,6 +90,8 @@ private: | |||
| 76 | [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); | 90 | [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); |
| 77 | 91 | ||
| 78 | [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); | 92 | [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); |
| 93 | [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline( | ||
| 94 | const BlitDepthStencilPipelineKey& key); | ||
| 79 | 95 | ||
| 80 | void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); | 96 | void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); |
| 81 | 97 | ||
| @@ -108,6 +124,7 @@ private: | |||
| 108 | vk::ShaderModule blit_depth_stencil_frag; | 124 | vk::ShaderModule blit_depth_stencil_frag; |
| 109 | vk::ShaderModule clear_color_vert; | 125 | vk::ShaderModule clear_color_vert; |
| 110 | vk::ShaderModule clear_color_frag; | 126 | vk::ShaderModule clear_color_frag; |
| 127 | vk::ShaderModule clear_stencil_frag; | ||
| 111 | vk::ShaderModule convert_depth_to_float_frag; | 128 | vk::ShaderModule convert_depth_to_float_frag; |
| 112 | vk::ShaderModule convert_float_to_depth_frag; | 129 | vk::ShaderModule convert_float_to_depth_frag; |
| 113 | vk::ShaderModule convert_abgr8_to_d24s8_frag; | 130 | vk::ShaderModule convert_abgr8_to_d24s8_frag; |
| @@ -122,6 +139,8 @@ private: | |||
| 122 | std::vector<vk::Pipeline> blit_depth_stencil_pipelines; | 139 | std::vector<vk::Pipeline> blit_depth_stencil_pipelines; |
| 123 | std::vector<BlitImagePipelineKey> clear_color_keys; | 140 | std::vector<BlitImagePipelineKey> clear_color_keys; |
| 124 | std::vector<vk::Pipeline> clear_color_pipelines; | 141 | std::vector<vk::Pipeline> clear_color_pipelines; |
| 142 | std::vector<BlitDepthStencilPipelineKey> clear_stencil_keys; | ||
| 143 | std::vector<vk::Pipeline> clear_stencil_pipelines; | ||
| 125 | vk::Pipeline convert_d32_to_r32_pipeline; | 144 | vk::Pipeline convert_d32_to_r32_pipeline; |
| 126 | vk::Pipeline convert_r32_to_d32_pipeline; | 145 | vk::Pipeline convert_r32_to_d32_pipeline; |
| 127 | vk::Pipeline convert_d16_to_r16_pipeline; | 146 | vk::Pipeline convert_d16_to_r16_pipeline; |
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index a8540339d..35bf80ea3 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp | |||
| @@ -126,7 +126,7 @@ struct FormatTuple { | |||
| 126 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM | 126 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM |
| 127 | {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM | 127 | {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM |
| 128 | {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT | 128 | {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT |
| 129 | {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM | 129 | {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable}, // A2R10G10B10_UNORM |
| 130 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) | 130 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) |
| 131 | {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) | 131 | {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) |
| 132 | {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM | 132 | {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 454bb66a4..c4c30d807 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp | |||
| @@ -66,21 +66,6 @@ std::string BuildCommaSeparatedExtensions( | |||
| 66 | return fmt::format("{}", fmt::join(available_extensions, ",")); | 66 | return fmt::format("{}", fmt::join(available_extensions, ",")); |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | DebugCallback MakeDebugCallback(const vk::Instance& instance, const vk::InstanceDispatch& dld) { | ||
| 70 | if (!Settings::values.renderer_debug) { | ||
| 71 | return DebugCallback{}; | ||
| 72 | } | ||
| 73 | const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld); | ||
| 74 | const auto it = std::ranges::find_if(*properties, [](const auto& prop) { | ||
| 75 | return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0; | ||
| 76 | }); | ||
| 77 | if (it != properties->end()) { | ||
| 78 | return CreateDebugUtilsCallback(instance); | ||
| 79 | } else { | ||
| 80 | return CreateDebugReportCallback(instance); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | } // Anonymous namespace | 69 | } // Anonymous namespace |
| 85 | 70 | ||
| 86 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, | 71 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, |
| @@ -103,7 +88,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, | |||
| 103 | cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), | 88 | cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), |
| 104 | instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, | 89 | instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, |
| 105 | Settings::values.renderer_debug.GetValue())), | 90 | Settings::values.renderer_debug.GetValue())), |
| 106 | debug_callback(MakeDebugCallback(instance, dld)), | 91 | debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance) |
| 92 | : vk::DebugUtilsMessenger{}), | ||
| 107 | surface(CreateSurface(instance, render_window.GetWindowInfo())), | 93 | surface(CreateSurface(instance, render_window.GetWindowInfo())), |
| 108 | device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), | 94 | device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), |
| 109 | scheduler(device, state_tracker), | 95 | scheduler(device, state_tracker), |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 89e98425e..590bc1c64 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h | |||
| @@ -35,8 +35,6 @@ class GPU; | |||
| 35 | 35 | ||
| 36 | namespace Vulkan { | 36 | namespace Vulkan { |
| 37 | 37 | ||
| 38 | using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>; | ||
| 39 | |||
| 40 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, | 38 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, |
| 41 | VkSurfaceKHR surface); | 39 | VkSurfaceKHR surface); |
| 42 | 40 | ||
| @@ -75,7 +73,7 @@ private: | |||
| 75 | vk::InstanceDispatch dld; | 73 | vk::InstanceDispatch dld; |
| 76 | 74 | ||
| 77 | vk::Instance instance; | 75 | vk::Instance instance; |
| 78 | DebugCallback debug_callback; | 76 | vk::DebugUtilsMessenger debug_messenger; |
| 79 | vk::SurfaceKHR surface; | 77 | vk::SurfaceKHR surface; |
| 80 | 78 | ||
| 81 | ScreenInfo screen_info; | 79 | ScreenInfo screen_info; |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 60a6ac651..e15865d16 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp | |||
| @@ -529,17 +529,20 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi | |||
| 529 | buffer_handles.push_back(handle); | 529 | buffer_handles.push_back(handle); |
| 530 | } | 530 | } |
| 531 | if (device.IsExtExtendedDynamicStateSupported()) { | 531 | if (device.IsExtExtendedDynamicStateSupported()) { |
| 532 | scheduler.Record([bindings_ = std::move(bindings), | 532 | scheduler.Record([this, bindings_ = std::move(bindings), |
| 533 | buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { | 533 | buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { |
| 534 | cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, | 534 | cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, |
| 535 | bindings_.max_index - bindings_.min_index, | 535 | std::min(bindings_.max_index - bindings_.min_index, |
| 536 | device.GetMaxVertexInputBindings()), | ||
| 536 | buffer_handles_.data(), bindings_.offsets.data(), | 537 | buffer_handles_.data(), bindings_.offsets.data(), |
| 537 | bindings_.sizes.data(), bindings_.strides.data()); | 538 | bindings_.sizes.data(), bindings_.strides.data()); |
| 538 | }); | 539 | }); |
| 539 | } else { | 540 | } else { |
| 540 | scheduler.Record([bindings_ = std::move(bindings), | 541 | scheduler.Record([this, bindings_ = std::move(bindings), |
| 541 | buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { | 542 | buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { |
| 542 | cmdbuf.BindVertexBuffers(bindings_.min_index, bindings_.max_index - bindings_.min_index, | 543 | cmdbuf.BindVertexBuffers(bindings_.min_index, |
| 544 | std::min(bindings_.max_index - bindings_.min_index, | ||
| 545 | device.GetMaxVertexInputBindings()), | ||
| 543 | buffer_handles_.data(), bindings_.offsets.data()); | 546 | buffer_handles_.data(), bindings_.offsets.data()); |
| 544 | }); | 547 | }); |
| 545 | } | 548 | } |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index c1314ca99..a1ec1a100 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -294,10 +294,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device | |||
| 294 | texture_cache{texture_cache_}, shader_notify{shader_notify_}, | 294 | texture_cache{texture_cache_}, shader_notify{shader_notify_}, |
| 295 | use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, | 295 | use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, |
| 296 | use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, | 296 | use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, |
| 297 | workers(device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY | 297 | #ifdef ANDROID |
| 298 | ? 1 | 298 | workers(1, "VkPipelineBuilder"), |
| 299 | : (std::max(std::thread::hardware_concurrency(), 2U) - 1), | 299 | #else |
| 300 | "VkPipelineBuilder"), | 300 | workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"), |
| 301 | #endif | ||
| 301 | serialization_thread(1, "VkPipelineSerialization") { | 302 | serialization_thread(1, "VkPipelineSerialization") { |
| 302 | const auto& float_control{device.FloatControlProperties()}; | 303 | const auto& float_control{device.FloatControlProperties()}; |
| 303 | const VkDriverId driver_id{device.GetDriverID()}; | 304 | const VkDriverId driver_id{device.GetDriverID()}; |
| @@ -611,9 +612,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( | |||
| 611 | 612 | ||
| 612 | const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; | 613 | const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; |
| 613 | Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); | 614 | Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); |
| 614 | if (Settings::values.dump_shaders) { | ||
| 615 | env.Dump(hash, key.unique_hashes[index]); | ||
| 616 | } | ||
| 617 | if (!uses_vertex_a || index != 1) { | 615 | if (!uses_vertex_a || index != 1) { |
| 618 | // Normal path | 616 | // Normal path |
| 619 | programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); | 617 | programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); |
| @@ -624,6 +622,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( | |||
| 624 | programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); | 622 | programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); |
| 625 | } | 623 | } |
| 626 | 624 | ||
| 625 | if (Settings::values.dump_shaders) { | ||
| 626 | env.Dump(hash, key.unique_hashes[index]); | ||
| 627 | } | ||
| 628 | |||
| 627 | if (programs[index].info.requires_layer_emulation) { | 629 | if (programs[index].info.requires_layer_emulation) { |
| 628 | layer_source_program = &programs[index]; | 630 | layer_source_program = &programs[index]; |
| 629 | } | 631 | } |
| @@ -664,6 +666,19 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( | |||
| 664 | std::move(modules), infos); | 666 | std::move(modules), infos); |
| 665 | 667 | ||
| 666 | } catch (const Shader::Exception& exception) { | 668 | } catch (const Shader::Exception& exception) { |
| 669 | auto hash = key.Hash(); | ||
| 670 | size_t env_index{0}; | ||
| 671 | for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { | ||
| 672 | if (key.unique_hashes[index] == 0) { | ||
| 673 | continue; | ||
| 674 | } | ||
| 675 | Shader::Environment& env{*envs[env_index]}; | ||
| 676 | ++env_index; | ||
| 677 | |||
| 678 | const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; | ||
| 679 | Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); | ||
| 680 | env.Dump(hash, key.unique_hashes[index]); | ||
| 681 | } | ||
| 667 | LOG_ERROR(Render_Vulkan, "{}", exception.what()); | 682 | LOG_ERROR(Render_Vulkan, "{}", exception.what()); |
| 668 | return nullptr; | 683 | return nullptr; |
| 669 | } | 684 | } |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index aa59889bd..01e76a82c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -428,15 +428,27 @@ void RasterizerVulkan::Clear(u32 layer_count) { | |||
| 428 | if (aspect_flags == 0) { | 428 | if (aspect_flags == 0) { |
| 429 | return; | 429 | return; |
| 430 | } | 430 | } |
| 431 | scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, | 431 | |
| 432 | clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { | 432 | if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { |
| 433 | VkClearAttachment attachment; | 433 | Region2D dst_region = { |
| 434 | attachment.aspectMask = aspect_flags; | 434 | Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, |
| 435 | attachment.colorAttachment = 0; | 435 | Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), |
| 436 | attachment.clearValue.depthStencil.depth = clear_depth; | 436 | .y = clear_rect.rect.offset.y + |
| 437 | attachment.clearValue.depthStencil.stencil = clear_stencil; | 437 | static_cast<s32>(clear_rect.rect.extent.height)}}; |
| 438 | cmdbuf.ClearAttachments(attachment, clear_rect); | 438 | blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth, |
| 439 | }); | 439 | static_cast<u8>(regs.stencil_front_mask), regs.clear_stencil, |
| 440 | regs.stencil_front_func_mask, dst_region); | ||
| 441 | } else { | ||
| 442 | scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, | ||
| 443 | clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { | ||
| 444 | VkClearAttachment attachment; | ||
| 445 | attachment.aspectMask = aspect_flags; | ||
| 446 | attachment.colorAttachment = 0; | ||
| 447 | attachment.clearValue.depthStencil.depth = clear_depth; | ||
| 448 | attachment.clearValue.depthStencil.stencil = clear_stencil; | ||
| 449 | cmdbuf.ClearAttachments(attachment, clear_rect); | ||
| 450 | }); | ||
| 451 | } | ||
| 440 | } | 452 | } |
| 441 | 453 | ||
| 442 | void RasterizerVulkan::DispatchCompute() { | 454 | void RasterizerVulkan::DispatchCompute() { |
| @@ -451,6 +463,20 @@ void RasterizerVulkan::DispatchCompute() { | |||
| 451 | pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); | 463 | pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); |
| 452 | 464 | ||
| 453 | const auto& qmd{kepler_compute->launch_description}; | 465 | const auto& qmd{kepler_compute->launch_description}; |
| 466 | auto indirect_address = kepler_compute->GetIndirectComputeAddress(); | ||
| 467 | if (indirect_address) { | ||
| 468 | // DispatchIndirect | ||
| 469 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 470 | const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite; | ||
| 471 | const auto [buffer, offset] = | ||
| 472 | buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); | ||
| 473 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 474 | scheduler.Record([indirect_buffer = buffer->Handle(), | ||
| 475 | indirect_offset = offset](vk::CommandBuffer cmdbuf) { | ||
| 476 | cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset); | ||
| 477 | }); | ||
| 478 | return; | ||
| 479 | } | ||
| 454 | const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; | 480 | const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; |
| 455 | scheduler.RequestOutsideRenderPassOperationContext(); | 481 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 456 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); | 482 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); |
| @@ -830,7 +856,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 830 | } | 856 | } |
| 831 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); | 857 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); |
| 832 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | 858 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; |
| 833 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; | 859 | const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing |
| 860 | : VideoCommon::ObtainBufferOperation::MarkAsWritten; | ||
| 834 | const auto [buffer, offset] = | 861 | const auto [buffer, offset] = |
| 835 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); | 862 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); |
| 836 | 863 | ||
| @@ -839,8 +866,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 839 | const std::span copy_span{©, 1}; | 866 | const std::span copy_span{©, 1}; |
| 840 | 867 | ||
| 841 | if constexpr (IS_IMAGE_UPLOAD) { | 868 | if constexpr (IS_IMAGE_UPLOAD) { |
| 869 | texture_cache.PrepareImage(image_id, true, false); | ||
| 842 | image->UploadMemory(buffer->Handle(), offset, copy_span); | 870 | image->UploadMemory(buffer->Handle(), offset, copy_span); |
| 843 | } else { | 871 | } else { |
| 872 | if (offset % BytesPerBlock(image->info.format)) { | ||
| 873 | return false; | ||
| 874 | } | ||
| 844 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, | 875 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, |
| 845 | buffer_operand.address, buffer_size); | 876 | buffer_operand.address, buffer_size); |
| 846 | } | 877 | } |
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 4457b366f..1bdb0def5 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -719,6 +719,7 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad | |||
| 719 | return nullptr; | 719 | return nullptr; |
| 720 | } | 720 | } |
| 721 | const auto& image_map_ids = it->second; | 721 | const auto& image_map_ids = it->second; |
| 722 | boost::container::small_vector<const ImageBase*, 4> valid_images; | ||
| 722 | for (const ImageMapId map_id : image_map_ids) { | 723 | for (const ImageMapId map_id : image_map_ids) { |
| 723 | const ImageMapView& map = slot_map_views[map_id]; | 724 | const ImageMapView& map = slot_map_views[map_id]; |
| 724 | const ImageBase& image = slot_images[map.image_id]; | 725 | const ImageBase& image = slot_images[map.image_id]; |
| @@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad | |||
| 728 | if (image.image_view_ids.empty()) { | 729 | if (image.image_view_ids.empty()) { |
| 729 | continue; | 730 | continue; |
| 730 | } | 731 | } |
| 731 | return &slot_image_views[image.image_view_ids.at(0)]; | 732 | valid_images.push_back(&image); |
| 732 | } | 733 | } |
| 734 | |||
| 735 | if (valid_images.size() == 1) [[likely]] { | ||
| 736 | return &slot_image_views[valid_images[0]->image_view_ids.at(0)]; | ||
| 737 | } | ||
| 738 | |||
| 739 | if (valid_images.size() > 0) [[unlikely]] { | ||
| 740 | std::ranges::sort(valid_images, [](const auto* a, const auto* b) { | ||
| 741 | return a->modification_tick > b->modification_tick; | ||
| 742 | }); | ||
| 743 | return &slot_image_views[valid_images[0]->image_view_ids.at(0)]; | ||
| 744 | } | ||
| 745 | |||
| 733 | return nullptr; | 746 | return nullptr; |
| 734 | } | 747 | } |
| 735 | 748 | ||
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index e9ec91265..a40825c9f 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h | |||
| @@ -243,6 +243,9 @@ public: | |||
| 243 | /// Create channel state. | 243 | /// Create channel state. |
| 244 | void CreateChannel(Tegra::Control::ChannelState& channel) final override; | 244 | void CreateChannel(Tegra::Control::ChannelState& channel) final override; |
| 245 | 245 | ||
| 246 | /// Prepare an image to be used | ||
| 247 | void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); | ||
| 248 | |||
| 246 | std::recursive_mutex mutex; | 249 | std::recursive_mutex mutex; |
| 247 | 250 | ||
| 248 | private: | 251 | private: |
| @@ -387,9 +390,6 @@ private: | |||
| 387 | /// Synchronize image aliases, copying data if needed | 390 | /// Synchronize image aliases, copying data if needed |
| 388 | void SynchronizeAliases(ImageId image_id); | 391 | void SynchronizeAliases(ImageId image_id); |
| 389 | 392 | ||
| 390 | /// Prepare an image to be used | ||
| 391 | void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); | ||
| 392 | |||
| 393 | /// Prepare an image view to be used | 393 | /// Prepare an image view to be used |
| 394 | void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); | 394 | void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); |
| 395 | 395 | ||
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp index 67e8065a4..448df2d3a 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp | |||
| @@ -63,22 +63,6 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, | |||
| 63 | return VK_FALSE; | 63 | return VK_FALSE; |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, | ||
| 67 | uint64_t object, size_t location, int32_t messageCode, | ||
| 68 | const char* pLayerPrefix, const char* pMessage, void* pUserData) { | ||
| 69 | const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags); | ||
| 70 | const std::string_view message{pMessage}; | ||
| 71 | if (severity & VK_DEBUG_REPORT_ERROR_BIT_EXT) { | ||
| 72 | LOG_CRITICAL(Render_Vulkan, "{}", message); | ||
| 73 | } else if (severity & VK_DEBUG_REPORT_WARNING_BIT_EXT) { | ||
| 74 | LOG_WARNING(Render_Vulkan, "{}", message); | ||
| 75 | } else if (severity & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { | ||
| 76 | LOG_INFO(Render_Vulkan, "{}", message); | ||
| 77 | } else if (severity & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { | ||
| 78 | LOG_DEBUG(Render_Vulkan, "{}", message); | ||
| 79 | } | ||
| 80 | return VK_FALSE; | ||
| 81 | } | ||
| 82 | } // Anonymous namespace | 66 | } // Anonymous namespace |
| 83 | 67 | ||
| 84 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { | 68 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { |
| @@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { | |||
| 98 | }); | 82 | }); |
| 99 | } | 83 | } |
| 100 | 84 | ||
| 101 | vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance) { | ||
| 102 | return instance.CreateDebugReportCallback({ | ||
| 103 | .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, | ||
| 104 | .pNext = nullptr, | ||
| 105 | .flags = VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT | | ||
| 106 | VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT, | ||
| 107 | .pfnCallback = DebugReportCallback, | ||
| 108 | .pUserData = nullptr, | ||
| 109 | }); | ||
| 110 | } | ||
| 111 | |||
| 112 | } // namespace Vulkan | 85 | } // namespace Vulkan |
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.h b/src/video_core/vulkan_common/vulkan_debug_callback.h index a8af7b406..5e940782f 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.h +++ b/src/video_core/vulkan_common/vulkan_debug_callback.h | |||
| @@ -9,6 +9,4 @@ namespace Vulkan { | |||
| 9 | 9 | ||
| 10 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); | 10 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); |
| 11 | 11 | ||
| 12 | vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance); | ||
| 13 | |||
| 14 | } // namespace Vulkan | 12 | } // namespace Vulkan |
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 710929ac5..617417040 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -71,6 +71,11 @@ constexpr std::array R8G8B8_SSCALED{ | |||
| 71 | VK_FORMAT_UNDEFINED, | 71 | VK_FORMAT_UNDEFINED, |
| 72 | }; | 72 | }; |
| 73 | 73 | ||
| 74 | constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{ | ||
| 75 | VK_FORMAT_R32G32B32A32_SFLOAT, | ||
| 76 | VK_FORMAT_UNDEFINED, | ||
| 77 | }; | ||
| 78 | |||
| 74 | } // namespace Alternatives | 79 | } // namespace Alternatives |
| 75 | 80 | ||
| 76 | enum class NvidiaArchitecture { | 81 | enum class NvidiaArchitecture { |
| @@ -103,6 +108,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { | |||
| 103 | return Alternatives::R16G16B16_SSCALED.data(); | 108 | return Alternatives::R16G16B16_SSCALED.data(); |
| 104 | case VK_FORMAT_R8G8B8_SSCALED: | 109 | case VK_FORMAT_R8G8B8_SSCALED: |
| 105 | return Alternatives::R8G8B8_SSCALED.data(); | 110 | return Alternatives::R8G8B8_SSCALED.data(); |
| 111 | case VK_FORMAT_R32G32B32_SFLOAT: | ||
| 112 | return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); | ||
| 106 | default: | 113 | default: |
| 107 | return nullptr; | 114 | return nullptr; |
| 108 | } | 115 | } |
| @@ -130,6 +137,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica | |||
| 130 | VK_FORMAT_A2B10G10R10_UINT_PACK32, | 137 | VK_FORMAT_A2B10G10R10_UINT_PACK32, |
| 131 | VK_FORMAT_A2B10G10R10_UNORM_PACK32, | 138 | VK_FORMAT_A2B10G10R10_UNORM_PACK32, |
| 132 | VK_FORMAT_A2B10G10R10_USCALED_PACK32, | 139 | VK_FORMAT_A2B10G10R10_USCALED_PACK32, |
| 140 | VK_FORMAT_A2R10G10B10_UNORM_PACK32, | ||
| 133 | VK_FORMAT_A8B8G8R8_SINT_PACK32, | 141 | VK_FORMAT_A8B8G8R8_SINT_PACK32, |
| 134 | VK_FORMAT_A8B8G8R8_SNORM_PACK32, | 142 | VK_FORMAT_A8B8G8R8_SNORM_PACK32, |
| 135 | VK_FORMAT_A8B8G8R8_SRGB_PACK32, | 143 | VK_FORMAT_A8B8G8R8_SRGB_PACK32, |
| @@ -326,6 +334,43 @@ std::vector<const char*> ExtensionListForVulkan( | |||
| 326 | 334 | ||
| 327 | } // Anonymous namespace | 335 | } // Anonymous namespace |
| 328 | 336 | ||
| 337 | void Device::RemoveExtension(bool& extension, const std::string& extension_name) { | ||
| 338 | extension = false; | ||
| 339 | loaded_extensions.erase(extension_name); | ||
| 340 | } | ||
| 341 | |||
| 342 | void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) { | ||
| 343 | if (loaded_extensions.contains(extension_name) && !is_suitable) { | ||
| 344 | LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name); | ||
| 345 | this->RemoveExtension(is_suitable, extension_name); | ||
| 346 | } | ||
| 347 | } | ||
| 348 | |||
| 349 | template <typename Feature> | ||
| 350 | void Device::RemoveExtensionFeature(bool& extension, Feature& feature, | ||
| 351 | const std::string& extension_name) { | ||
| 352 | // Unload extension. | ||
| 353 | this->RemoveExtension(extension, extension_name); | ||
| 354 | |||
| 355 | // Save sType and pNext for chain. | ||
| 356 | VkStructureType sType = feature.sType; | ||
| 357 | void* pNext = feature.pNext; | ||
| 358 | |||
| 359 | // Clear feature struct and restore chain. | ||
| 360 | feature = {}; | ||
| 361 | feature.sType = sType; | ||
| 362 | feature.pNext = pNext; | ||
| 363 | } | ||
| 364 | |||
| 365 | template <typename Feature> | ||
| 366 | void Device::RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature, | ||
| 367 | const std::string& extension_name) { | ||
| 368 | if (loaded_extensions.contains(extension_name) && !is_suitable) { | ||
| 369 | LOG_WARNING(Render_Vulkan, "Removing features for unsuitable extension {}", extension_name); | ||
| 370 | this->RemoveExtensionFeature(is_suitable, feature, extension_name); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 329 | Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, | 374 | Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, |
| 330 | const vk::InstanceDispatch& dld_) | 375 | const vk::InstanceDispatch& dld_) |
| 331 | : instance{instance_}, dld{dld_}, physical{physical_}, | 376 | : instance{instance_}, dld{dld_}, physical{physical_}, |
| @@ -397,21 +442,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 397 | if (is_qualcomm || is_turnip) { | 442 | if (is_qualcomm || is_turnip) { |
| 398 | LOG_WARNING(Render_Vulkan, | 443 | LOG_WARNING(Render_Vulkan, |
| 399 | "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); | 444 | "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); |
| 400 | extensions.custom_border_color = false; | 445 | RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, |
| 401 | loaded_extensions.erase(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); | 446 | VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); |
| 402 | } | 447 | } |
| 403 | 448 | ||
| 404 | if (is_qualcomm) { | 449 | if (is_qualcomm) { |
| 405 | must_emulate_scaled_formats = true; | 450 | must_emulate_scaled_formats = true; |
| 406 | 451 | ||
| 407 | LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state"); | 452 | LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state"); |
| 408 | extensions.extended_dynamic_state = false; | 453 | RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, |
| 409 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | 454 | VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); |
| 410 | 455 | ||
| 411 | LOG_WARNING(Render_Vulkan, | 456 | LOG_WARNING(Render_Vulkan, |
| 412 | "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation"); | 457 | "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation"); |
| 413 | extensions.push_descriptor = false; | 458 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 414 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | ||
| 415 | 459 | ||
| 416 | #if defined(ANDROID) && defined(ARCHITECTURE_arm64) | 460 | #if defined(ANDROID) && defined(ARCHITECTURE_arm64) |
| 417 | // Patch the driver to enable BCn textures. | 461 | // Patch the driver to enable BCn textures. |
| @@ -440,15 +484,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 440 | must_emulate_scaled_formats = true; | 484 | must_emulate_scaled_formats = true; |
| 441 | 485 | ||
| 442 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); | 486 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); |
| 443 | extensions.extended_dynamic_state = false; | 487 | RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, |
| 444 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | 488 | VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); |
| 445 | 489 | ||
| 446 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); | 490 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); |
| 447 | features.extended_dynamic_state2.extendedDynamicState2 = false; | 491 | RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, |
| 448 | features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; | 492 | VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); |
| 449 | features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; | ||
| 450 | extensions.extended_dynamic_state2 = false; | ||
| 451 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | ||
| 452 | } | 493 | } |
| 453 | 494 | ||
| 454 | if (is_nvidia) { | 495 | if (is_nvidia) { |
| @@ -464,8 +505,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 464 | case NvidiaArchitecture::VoltaOrOlder: | 505 | case NvidiaArchitecture::VoltaOrOlder: |
| 465 | if (nv_major_version < 527) { | 506 | if (nv_major_version < 527) { |
| 466 | LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); | 507 | LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); |
| 467 | extensions.push_descriptor = false; | 508 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 468 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | ||
| 469 | } | 509 | } |
| 470 | break; | 510 | break; |
| 471 | } | 511 | } |
| @@ -480,8 +520,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 480 | if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) { | 520 | if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) { |
| 481 | LOG_WARNING(Render_Vulkan, | 521 | LOG_WARNING(Render_Vulkan, |
| 482 | "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state"); | 522 | "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state"); |
| 483 | extensions.extended_dynamic_state = false; | 523 | RemoveExtensionFeature(extensions.extended_dynamic_state, |
| 484 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | 524 | features.extended_dynamic_state, |
| 525 | VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | ||
| 485 | } | 526 | } |
| 486 | } | 527 | } |
| 487 | if (extensions.extended_dynamic_state2 && is_radv) { | 528 | if (extensions.extended_dynamic_state2 && is_radv) { |
| @@ -490,11 +531,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 490 | LOG_WARNING( | 531 | LOG_WARNING( |
| 491 | Render_Vulkan, | 532 | Render_Vulkan, |
| 492 | "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2"); | 533 | "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2"); |
| 493 | features.extended_dynamic_state2.extendedDynamicState2 = false; | 534 | RemoveExtensionFeature(extensions.extended_dynamic_state2, |
| 494 | features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; | 535 | features.extended_dynamic_state2, |
| 495 | features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; | 536 | VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); |
| 496 | extensions.extended_dynamic_state2 = false; | ||
| 497 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | ||
| 498 | } | 537 | } |
| 499 | } | 538 | } |
| 500 | if (extensions.extended_dynamic_state2 && is_qualcomm) { | 539 | if (extensions.extended_dynamic_state2 && is_qualcomm) { |
| @@ -504,11 +543,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 504 | // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2. | 543 | // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2. |
| 505 | LOG_WARNING(Render_Vulkan, | 544 | LOG_WARNING(Render_Vulkan, |
| 506 | "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2"); | 545 | "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2"); |
| 507 | features.extended_dynamic_state2.extendedDynamicState2 = false; | 546 | RemoveExtensionFeature(extensions.extended_dynamic_state2, |
| 508 | features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; | 547 | features.extended_dynamic_state2, |
| 509 | features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; | 548 | VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); |
| 510 | extensions.extended_dynamic_state2 = false; | ||
| 511 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | ||
| 512 | } | 549 | } |
| 513 | } | 550 | } |
| 514 | if (extensions.extended_dynamic_state3 && is_radv) { | 551 | if (extensions.extended_dynamic_state3 && is_radv) { |
| @@ -540,9 +577,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 540 | if (is_rdna2) { | 577 | if (is_rdna2) { |
| 541 | LOG_WARNING(Render_Vulkan, | 578 | LOG_WARNING(Render_Vulkan, |
| 542 | "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware"); | 579 | "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware"); |
| 543 | features.vertex_input_dynamic_state.vertexInputDynamicState = false; | 580 | RemoveExtensionFeature(extensions.vertex_input_dynamic_state, |
| 544 | extensions.vertex_input_dynamic_state = false; | 581 | features.vertex_input_dynamic_state, |
| 545 | loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); | 582 | VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); |
| 546 | } | 583 | } |
| 547 | } | 584 | } |
| 548 | if (extensions.vertex_input_dynamic_state && is_qualcomm) { | 585 | if (extensions.vertex_input_dynamic_state && is_qualcomm) { |
| @@ -553,9 +590,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 553 | LOG_WARNING( | 590 | LOG_WARNING( |
| 554 | Render_Vulkan, | 591 | Render_Vulkan, |
| 555 | "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state"); | 592 | "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state"); |
| 556 | features.vertex_input_dynamic_state.vertexInputDynamicState = false; | 593 | RemoveExtensionFeature(extensions.vertex_input_dynamic_state, |
| 557 | extensions.vertex_input_dynamic_state = false; | 594 | features.vertex_input_dynamic_state, |
| 558 | loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); | 595 | VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); |
| 559 | } | 596 | } |
| 560 | } | 597 | } |
| 561 | 598 | ||
| @@ -575,8 +612,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 575 | if (!features.shader_float16_int8.shaderFloat16) { | 612 | if (!features.shader_float16_int8.shaderFloat16) { |
| 576 | LOG_WARNING(Render_Vulkan, | 613 | LOG_WARNING(Render_Vulkan, |
| 577 | "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax"); | 614 | "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax"); |
| 578 | extensions.sampler_filter_minmax = false; | 615 | RemoveExtension(extensions.sampler_filter_minmax, |
| 579 | loaded_extensions.erase(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); | 616 | VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); |
| 580 | } | 617 | } |
| 581 | } | 618 | } |
| 582 | 619 | ||
| @@ -584,8 +621,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 584 | const u32 version = (properties.properties.driverVersion << 3) >> 3; | 621 | const u32 version = (properties.properties.driverVersion << 3) >> 3; |
| 585 | if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) { | 622 | if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) { |
| 586 | LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state"); | 623 | LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state"); |
| 587 | extensions.vertex_input_dynamic_state = false; | 624 | RemoveExtensionFeature(extensions.vertex_input_dynamic_state, |
| 588 | loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); | 625 | features.vertex_input_dynamic_state, |
| 626 | VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); | ||
| 589 | } | 627 | } |
| 590 | } | 628 | } |
| 591 | if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) { | 629 | if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) { |
| @@ -612,8 +650,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 612 | // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc | 650 | // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc |
| 613 | LOG_WARNING(Render_Vulkan, | 651 | LOG_WARNING(Render_Vulkan, |
| 614 | "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); | 652 | "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); |
| 615 | extensions.push_descriptor = false; | 653 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 616 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | ||
| 617 | } | 654 | } |
| 618 | } | 655 | } |
| 619 | if (is_mvk) { | 656 | if (is_mvk) { |
| @@ -1007,34 +1044,29 @@ bool Device::GetSuitability(bool requires_swapchain) { | |||
| 1007 | return suitable; | 1044 | return suitable; |
| 1008 | } | 1045 | } |
| 1009 | 1046 | ||
| 1010 | void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) { | ||
| 1011 | if (loaded_extensions.contains(extension_name) && !is_suitable) { | ||
| 1012 | LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name); | ||
| 1013 | loaded_extensions.erase(extension_name); | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | void Device::RemoveUnsuitableExtensions() { | 1047 | void Device::RemoveUnsuitableExtensions() { |
| 1018 | // VK_EXT_custom_border_color | 1048 | // VK_EXT_custom_border_color |
| 1019 | extensions.custom_border_color = features.custom_border_color.customBorderColors && | 1049 | extensions.custom_border_color = features.custom_border_color.customBorderColors && |
| 1020 | features.custom_border_color.customBorderColorWithoutFormat; | 1050 | features.custom_border_color.customBorderColorWithoutFormat; |
| 1021 | RemoveExtensionIfUnsuitable(extensions.custom_border_color, | 1051 | RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, |
| 1022 | VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); | 1052 | VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); |
| 1023 | 1053 | ||
| 1024 | // VK_EXT_depth_clip_control | 1054 | // VK_EXT_depth_clip_control |
| 1025 | extensions.depth_clip_control = features.depth_clip_control.depthClipControl; | 1055 | extensions.depth_clip_control = features.depth_clip_control.depthClipControl; |
| 1026 | RemoveExtensionIfUnsuitable(extensions.depth_clip_control, | 1056 | RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, |
| 1027 | VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); | 1057 | VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); |
| 1028 | 1058 | ||
| 1029 | // VK_EXT_extended_dynamic_state | 1059 | // VK_EXT_extended_dynamic_state |
| 1030 | extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; | 1060 | extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; |
| 1031 | RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state, | 1061 | RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, |
| 1032 | VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | 1062 | features.extended_dynamic_state, |
| 1063 | VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | ||
| 1033 | 1064 | ||
| 1034 | // VK_EXT_extended_dynamic_state2 | 1065 | // VK_EXT_extended_dynamic_state2 |
| 1035 | extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2; | 1066 | extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2; |
| 1036 | RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state2, | 1067 | RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state2, |
| 1037 | VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | 1068 | features.extended_dynamic_state2, |
| 1069 | VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | ||
| 1038 | 1070 | ||
| 1039 | // VK_EXT_extended_dynamic_state3 | 1071 | // VK_EXT_extended_dynamic_state3 |
| 1040 | dynamic_state3_blending = | 1072 | dynamic_state3_blending = |
| @@ -1048,35 +1080,38 @@ void Device::RemoveUnsuitableExtensions() { | |||
| 1048 | extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables; | 1080 | extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables; |
| 1049 | dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3; | 1081 | dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3; |
| 1050 | dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3; | 1082 | dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3; |
| 1051 | RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state3, | 1083 | RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3, |
| 1052 | VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); | 1084 | features.extended_dynamic_state3, |
| 1085 | VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); | ||
| 1053 | 1086 | ||
| 1054 | // VK_EXT_provoking_vertex | 1087 | // VK_EXT_provoking_vertex |
| 1055 | extensions.provoking_vertex = | 1088 | extensions.provoking_vertex = |
| 1056 | features.provoking_vertex.provokingVertexLast && | 1089 | features.provoking_vertex.provokingVertexLast && |
| 1057 | features.provoking_vertex.transformFeedbackPreservesProvokingVertex; | 1090 | features.provoking_vertex.transformFeedbackPreservesProvokingVertex; |
| 1058 | RemoveExtensionIfUnsuitable(extensions.provoking_vertex, | 1091 | RemoveExtensionFeatureIfUnsuitable(extensions.provoking_vertex, features.provoking_vertex, |
| 1059 | VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); | 1092 | VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); |
| 1060 | 1093 | ||
| 1061 | // VK_KHR_shader_atomic_int64 | 1094 | // VK_KHR_shader_atomic_int64 |
| 1062 | extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && | 1095 | extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && |
| 1063 | features.shader_atomic_int64.shaderSharedInt64Atomics; | 1096 | features.shader_atomic_int64.shaderSharedInt64Atomics; |
| 1064 | RemoveExtensionIfUnsuitable(extensions.shader_atomic_int64, | 1097 | RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64, |
| 1065 | VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); | 1098 | VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); |
| 1066 | 1099 | ||
| 1067 | // VK_EXT_shader_demote_to_helper_invocation | 1100 | // VK_EXT_shader_demote_to_helper_invocation |
| 1068 | extensions.shader_demote_to_helper_invocation = | 1101 | extensions.shader_demote_to_helper_invocation = |
| 1069 | features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation; | 1102 | features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation; |
| 1070 | RemoveExtensionIfUnsuitable(extensions.shader_demote_to_helper_invocation, | 1103 | RemoveExtensionFeatureIfUnsuitable(extensions.shader_demote_to_helper_invocation, |
| 1071 | VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); | 1104 | features.shader_demote_to_helper_invocation, |
| 1105 | VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); | ||
| 1072 | 1106 | ||
| 1073 | // VK_EXT_subgroup_size_control | 1107 | // VK_EXT_subgroup_size_control |
| 1074 | extensions.subgroup_size_control = | 1108 | extensions.subgroup_size_control = |
| 1075 | features.subgroup_size_control.subgroupSizeControl && | 1109 | features.subgroup_size_control.subgroupSizeControl && |
| 1076 | properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize && | 1110 | properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize && |
| 1077 | properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize; | 1111 | properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize; |
| 1078 | RemoveExtensionIfUnsuitable(extensions.subgroup_size_control, | 1112 | RemoveExtensionFeatureIfUnsuitable(extensions.subgroup_size_control, |
| 1079 | VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); | 1113 | features.subgroup_size_control, |
| 1114 | VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); | ||
| 1080 | 1115 | ||
| 1081 | // VK_EXT_transform_feedback | 1116 | // VK_EXT_transform_feedback |
| 1082 | extensions.transform_feedback = | 1117 | extensions.transform_feedback = |
| @@ -1086,24 +1121,27 @@ void Device::RemoveUnsuitableExtensions() { | |||
| 1086 | properties.transform_feedback.maxTransformFeedbackBuffers > 0 && | 1121 | properties.transform_feedback.maxTransformFeedbackBuffers > 0 && |
| 1087 | properties.transform_feedback.transformFeedbackQueries && | 1122 | properties.transform_feedback.transformFeedbackQueries && |
| 1088 | properties.transform_feedback.transformFeedbackDraw; | 1123 | properties.transform_feedback.transformFeedbackDraw; |
| 1089 | RemoveExtensionIfUnsuitable(extensions.transform_feedback, | 1124 | RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback, |
| 1090 | VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); | 1125 | VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); |
| 1091 | 1126 | ||
| 1092 | // VK_EXT_vertex_input_dynamic_state | 1127 | // VK_EXT_vertex_input_dynamic_state |
| 1093 | extensions.vertex_input_dynamic_state = | 1128 | extensions.vertex_input_dynamic_state = |
| 1094 | features.vertex_input_dynamic_state.vertexInputDynamicState; | 1129 | features.vertex_input_dynamic_state.vertexInputDynamicState; |
| 1095 | RemoveExtensionIfUnsuitable(extensions.vertex_input_dynamic_state, | 1130 | RemoveExtensionFeatureIfUnsuitable(extensions.vertex_input_dynamic_state, |
| 1096 | VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); | 1131 | features.vertex_input_dynamic_state, |
| 1132 | VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); | ||
| 1097 | 1133 | ||
| 1098 | // VK_KHR_pipeline_executable_properties | 1134 | // VK_KHR_pipeline_executable_properties |
| 1099 | if (Settings::values.renderer_shader_feedback.GetValue()) { | 1135 | if (Settings::values.renderer_shader_feedback.GetValue()) { |
| 1100 | extensions.pipeline_executable_properties = | 1136 | extensions.pipeline_executable_properties = |
| 1101 | features.pipeline_executable_properties.pipelineExecutableInfo; | 1137 | features.pipeline_executable_properties.pipelineExecutableInfo; |
| 1102 | RemoveExtensionIfUnsuitable(extensions.pipeline_executable_properties, | 1138 | RemoveExtensionFeatureIfUnsuitable(extensions.pipeline_executable_properties, |
| 1103 | VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); | 1139 | features.pipeline_executable_properties, |
| 1140 | VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); | ||
| 1104 | } else { | 1141 | } else { |
| 1105 | extensions.pipeline_executable_properties = false; | 1142 | RemoveExtensionFeature(extensions.pipeline_executable_properties, |
| 1106 | loaded_extensions.erase(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); | 1143 | features.pipeline_executable_properties, |
| 1144 | VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); | ||
| 1107 | } | 1145 | } |
| 1108 | 1146 | ||
| 1109 | // VK_KHR_workgroup_memory_explicit_layout | 1147 | // VK_KHR_workgroup_memory_explicit_layout |
| @@ -1113,8 +1151,9 @@ void Device::RemoveUnsuitableExtensions() { | |||
| 1113 | features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && | 1151 | features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && |
| 1114 | features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && | 1152 | features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && |
| 1115 | features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; | 1153 | features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; |
| 1116 | RemoveExtensionIfUnsuitable(extensions.workgroup_memory_explicit_layout, | 1154 | RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout, |
| 1117 | VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); | 1155 | features.workgroup_memory_explicit_layout, |
| 1156 | VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); | ||
| 1118 | } | 1157 | } |
| 1119 | 1158 | ||
| 1120 | void Device::SetupFamilies(VkSurfaceKHR surface) { | 1159 | void Device::SetupFamilies(VkSurfaceKHR surface) { |
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d8dd41e51..488fdd313 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h | |||
| @@ -639,8 +639,17 @@ private: | |||
| 639 | 639 | ||
| 640 | // Remove extensions which have incomplete feature support. | 640 | // Remove extensions which have incomplete feature support. |
| 641 | void RemoveUnsuitableExtensions(); | 641 | void RemoveUnsuitableExtensions(); |
| 642 | |||
| 643 | void RemoveExtension(bool& extension, const std::string& extension_name); | ||
| 642 | void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name); | 644 | void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name); |
| 643 | 645 | ||
| 646 | template <typename Feature> | ||
| 647 | void RemoveExtensionFeature(bool& extension, Feature& feature, | ||
| 648 | const std::string& extension_name); | ||
| 649 | template <typename Feature> | ||
| 650 | void RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature, | ||
| 651 | const std::string& extension_name); | ||
| 652 | |||
| 644 | /// Sets up queue families. | 653 | /// Sets up queue families. |
| 645 | void SetupFamilies(VkSurfaceKHR surface); | 654 | void SetupFamilies(VkSurfaceKHR surface); |
| 646 | 655 | ||
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp index 72aedb8d8..180657a75 100644 --- a/src/video_core/vulkan_common/vulkan_instance.cpp +++ b/src/video_core/vulkan_common/vulkan_instance.cpp | |||
| @@ -41,9 +41,6 @@ namespace { | |||
| 41 | bool enable_validation) { | 41 | bool enable_validation) { |
| 42 | std::vector<const char*> extensions; | 42 | std::vector<const char*> extensions; |
| 43 | extensions.reserve(6); | 43 | extensions.reserve(6); |
| 44 | #ifdef __APPLE__ | ||
| 45 | extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); | ||
| 46 | #endif | ||
| 47 | switch (window_type) { | 44 | switch (window_type) { |
| 48 | case Core::Frontend::WindowSystemType::Headless: | 45 | case Core::Frontend::WindowSystemType::Headless: |
| 49 | break; | 46 | break; |
| @@ -74,11 +71,14 @@ namespace { | |||
| 74 | if (window_type != Core::Frontend::WindowSystemType::Headless) { | 71 | if (window_type != Core::Frontend::WindowSystemType::Headless) { |
| 75 | extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); | 72 | extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); |
| 76 | } | 73 | } |
| 77 | if (enable_validation) { | 74 | #ifdef __APPLE__ |
| 78 | const bool debug_utils = | 75 | if (AreExtensionsSupported(dld, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) { |
| 79 | AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}); | 76 | extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); |
| 80 | extensions.push_back(debug_utils ? VK_EXT_DEBUG_UTILS_EXTENSION_NAME | 77 | } |
| 81 | : VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | 78 | #endif |
| 79 | if (enable_validation && | ||
| 80 | AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) { | ||
| 81 | extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); | ||
| 82 | } | 82 | } |
| 83 | return extensions; | 83 | return extensions; |
| 84 | } | 84 | } |
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp index 47f6f2a03..0130f6a0d 100644 --- a/src/video_core/vulkan_common/vulkan_library.cpp +++ b/src/video_core/vulkan_common/vulkan_library.cpp | |||
| @@ -19,13 +19,17 @@ std::shared_ptr<Common::DynamicLibrary> OpenLibrary( | |||
| 19 | #else | 19 | #else |
| 20 | auto library = std::make_shared<Common::DynamicLibrary>(); | 20 | auto library = std::make_shared<Common::DynamicLibrary>(); |
| 21 | #ifdef __APPLE__ | 21 | #ifdef __APPLE__ |
| 22 | const auto libvulkan_filename = | ||
| 23 | Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.1.dylib"; | ||
| 24 | const auto libmoltenvk_filename = | ||
| 25 | Common::FS::GetBundleDirectory() / "Contents/Frameworks/libMoltenVK.dylib"; | ||
| 26 | const char* library_paths[] = {std::getenv("LIBVULKAN_PATH"), libvulkan_filename.c_str(), | ||
| 27 | libmoltenvk_filename.c_str()}; | ||
| 22 | // Check if a path to a specific Vulkan library has been specified. | 28 | // Check if a path to a specific Vulkan library has been specified. |
| 23 | char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); | 29 | for (const auto& library_path : library_paths) { |
| 24 | if (!libvulkan_env || !library->Open(libvulkan_env)) { | 30 | if (library_path && library->Open(library_path)) { |
| 25 | // Use the libvulkan.dylib from the application bundle. | 31 | break; |
| 26 | const auto filename = | 32 | } |
| 27 | Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib"; | ||
| 28 | void(library->Open(Common::FS::PathToUTF8String(filename).c_str())); | ||
| 29 | } | 33 | } |
| 30 | #else | 34 | #else |
| 31 | std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); | 35 | std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 78e5a248f..c3f388d89 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp | |||
| @@ -92,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 92 | X(vkCmdCopyImage); | 92 | X(vkCmdCopyImage); |
| 93 | X(vkCmdCopyImageToBuffer); | 93 | X(vkCmdCopyImageToBuffer); |
| 94 | X(vkCmdDispatch); | 94 | X(vkCmdDispatch); |
| 95 | X(vkCmdDispatchIndirect); | ||
| 95 | X(vkCmdDraw); | 96 | X(vkCmdDraw); |
| 96 | X(vkCmdDrawIndexed); | 97 | X(vkCmdDrawIndexed); |
| 97 | X(vkCmdDrawIndirect); | 98 | X(vkCmdDrawIndirect); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index c226a2a29..049fa8038 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h | |||
| @@ -203,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; | 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; |
| 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; | 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; |
| 205 | PFN_vkCmdDispatch vkCmdDispatch{}; | 205 | PFN_vkCmdDispatch vkCmdDispatch{}; |
| 206 | PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; | ||
| 206 | PFN_vkCmdDraw vkCmdDraw{}; | 207 | PFN_vkCmdDraw vkCmdDraw{}; |
| 207 | PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; | 208 | PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; |
| 208 | PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; | 209 | PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; |
| @@ -1209,6 +1210,10 @@ public: | |||
| 1209 | dld->vkCmdDispatch(handle, x, y, z); | 1210 | dld->vkCmdDispatch(handle, x, y, z); |
| 1210 | } | 1211 | } |
| 1211 | 1212 | ||
| 1213 | void DispatchIndirect(VkBuffer indirect_buffer, VkDeviceSize offset) const noexcept { | ||
| 1214 | dld->vkCmdDispatchIndirect(handle, indirect_buffer, offset); | ||
| 1215 | } | ||
| 1216 | |||
| 1212 | void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, | 1217 | void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, |
| 1213 | VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, | 1218 | VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, |
| 1214 | Span<VkBufferMemoryBarrier> buffer_barriers, | 1219 | Span<VkBufferMemoryBarrier> buffer_barriers, |
diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp index 129eb1968..f88f67620 100644 --- a/src/web_service/verify_user_jwt.cpp +++ b/src/web_service/verify_user_jwt.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #if defined(__GNUC__) || defined(__clang__) | 4 | #if defined(__GNUC__) || defined(__clang__) |
| 5 | #pragma GCC diagnostic push | 5 | #pragma GCC diagnostic push |
| 6 | #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" | 6 | #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" |
| 7 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // for deprecated OpenSSL functions | ||
| 7 | #endif | 8 | #endif |
| 8 | #include <jwt/jwt.hpp> | 9 | #include <jwt/jwt.hpp> |
| 9 | #if defined(__GNUC__) || defined(__clang__) | 10 | #if defined(__GNUC__) || defined(__clang__) |
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 2e4da696c..8f86a1553 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -313,6 +313,18 @@ if (APPLE) | |||
| 313 | target_sources(yuzu PRIVATE ${MACOSX_ICON}) | 313 | target_sources(yuzu PRIVATE ${MACOSX_ICON}) |
| 314 | set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) | 314 | set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) |
| 315 | set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) | 315 | set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) |
| 316 | |||
| 317 | if (NOT USE_SYSTEM_MOLTENVK) | ||
| 318 | set(MOLTENVK_PLATFORM "macOS") | ||
| 319 | set(MOLTENVK_VERSION "v1.2.5") | ||
| 320 | download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION}) | ||
| 321 | endif() | ||
| 322 | find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED) | ||
| 323 | message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") | ||
| 324 | set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks | ||
| 325 | XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") | ||
| 326 | target_sources(yuzu PRIVATE ${MOLTENVK_LIBRARY}) | ||
| 327 | |||
| 316 | elseif(WIN32) | 328 | elseif(WIN32) |
| 317 | # compile as a win32 gui application instead of a console application | 329 | # compile as a win32 gui application instead of a console application |
| 318 | if (QT_VERSION VERSION_GREATER_EQUAL 6) | 330 | if (QT_VERSION VERSION_GREATER_EQUAL 6) |
diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp index 4988fcc83..b457a736a 100644 --- a/src/yuzu/applets/qt_amiibo_settings.cpp +++ b/src/yuzu/applets/qt_amiibo_settings.cpp | |||
| @@ -160,7 +160,8 @@ void QtAmiiboSettingsDialog::LoadAmiiboData() { | |||
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | const auto amiibo_name = std::string(register_info.amiibo_name.data()); | 162 | const auto amiibo_name = std::string(register_info.amiibo_name.data()); |
| 163 | const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data()); | 163 | const auto owner_name = |
| 164 | Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data()); | ||
| 164 | const auto creation_date = | 165 | const auto creation_date = |
| 165 | QDate(register_info.creation_date.year, register_info.creation_date.month, | 166 | QDate(register_info.creation_date.year, register_info.creation_date.month, |
| 166 | register_info.creation_date.day); | 167 | register_info.creation_date.day); |
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index 00aafb8f8..d15559518 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp | |||
| @@ -5,6 +5,8 @@ | |||
| 5 | #include <thread> | 5 | #include <thread> |
| 6 | 6 | ||
| 7 | #include "common/assert.h" | 7 | #include "common/assert.h" |
| 8 | #include "common/settings.h" | ||
| 9 | #include "common/settings_enums.h" | ||
| 8 | #include "common/string_util.h" | 10 | #include "common/string_util.h" |
| 9 | #include "core/core.h" | 11 | #include "core/core.h" |
| 10 | #include "core/hid/emulated_controller.h" | 12 | #include "core/hid/emulated_controller.h" |
| @@ -226,9 +228,11 @@ int QtControllerSelectorDialog::exec() { | |||
| 226 | } | 228 | } |
| 227 | 229 | ||
| 228 | void QtControllerSelectorDialog::ApplyConfiguration() { | 230 | void QtControllerSelectorDialog::ApplyConfiguration() { |
| 229 | const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); | 231 | const bool pre_docked_mode = Settings::IsDockedMode(); |
| 230 | Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); | 232 | const bool docked_mode_selected = ui->radioDocked->isChecked(); |
| 231 | OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system); | 233 | Settings::values.use_docked_mode.SetValue( |
| 234 | docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld); | ||
| 235 | OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system); | ||
| 232 | 236 | ||
| 233 | Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); | 237 | Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); |
| 234 | Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); | 238 | Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); |
| @@ -616,8 +620,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { | |||
| 616 | ui->radioDocked->setEnabled(!is_handheld); | 620 | ui->radioDocked->setEnabled(!is_handheld); |
| 617 | ui->radioUndocked->setEnabled(!is_handheld); | 621 | ui->radioUndocked->setEnabled(!is_handheld); |
| 618 | 622 | ||
| 619 | ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); | 623 | ui->radioDocked->setChecked(Settings::IsDockedMode()); |
| 620 | ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); | 624 | ui->radioUndocked->setChecked(!Settings::IsDockedMode()); |
| 621 | 625 | ||
| 622 | // Also force into undocked mode if the controller type is handheld. | 626 | // Also force into undocked mode if the controller type is handheld. |
| 623 | if (is_handheld) { | 627 | if (is_handheld) { |
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index bdd1497b5..2afa72140 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp | |||
| @@ -11,6 +11,8 @@ | |||
| 11 | #include <glad/glad.h> | 11 | #include <glad/glad.h> |
| 12 | 12 | ||
| 13 | #include <QtCore/qglobal.h> | 13 | #include <QtCore/qglobal.h> |
| 14 | #include "common/settings_enums.h" | ||
| 15 | #include "uisettings.h" | ||
| 14 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA | 16 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA |
| 15 | #include <QCamera> | 17 | #include <QCamera> |
| 16 | #include <QCameraImageCapture> | 18 | #include <QCameraImageCapture> |
| @@ -916,7 +918,6 @@ void GRenderWindow::ReleaseRenderTarget() { | |||
| 916 | 918 | ||
| 917 | void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { | 919 | void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { |
| 918 | auto& renderer = system.Renderer(); | 920 | auto& renderer = system.Renderer(); |
| 919 | const f32 res_scale = Settings::values.resolution_info.up_factor; | ||
| 920 | 921 | ||
| 921 | if (renderer.IsScreenshotPending()) { | 922 | if (renderer.IsScreenshotPending()) { |
| 922 | LOG_WARNING(Render, | 923 | LOG_WARNING(Render, |
| @@ -924,7 +925,18 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { | |||
| 924 | return; | 925 | return; |
| 925 | } | 926 | } |
| 926 | 927 | ||
| 927 | const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)}; | 928 | const Layout::FramebufferLayout layout{[]() { |
| 929 | u32 height = UISettings::values.screenshot_height.GetValue(); | ||
| 930 | if (height == 0) { | ||
| 931 | height = Settings::IsDockedMode() ? Layout::ScreenDocked::Height | ||
| 932 | : Layout::ScreenUndocked::Height; | ||
| 933 | height *= Settings::values.resolution_info.up_factor; | ||
| 934 | } | ||
| 935 | const u32 width = | ||
| 936 | UISettings::CalculateWidth(height, Settings::values.aspect_ratio.GetValue()); | ||
| 937 | return Layout::DefaultFrameLayout(width, height); | ||
| 938 | }()}; | ||
| 939 | |||
| 928 | screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); | 940 | screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); |
| 929 | renderer.RequestScreenshot( | 941 | renderer.RequestScreenshot( |
| 930 | screenshot_image.bits(), | 942 | screenshot_image.bits(), |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index b2405f9b8..1de093447 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "common/settings.h" | 10 | #include "common/settings.h" |
| 11 | #include "common/settings_common.h" | 11 | #include "common/settings_common.h" |
| 12 | #include "common/settings_enums.h" | ||
| 12 | #include "core/core.h" | 13 | #include "core/core.h" |
| 13 | #include "core/hle/service/acc/profile_manager.h" | 14 | #include "core/hle/service/acc/profile_manager.h" |
| 14 | #include "core/hle/service/hid/controllers/npad.h" | 15 | #include "core/hle/service/hid/controllers/npad.h" |
| @@ -85,9 +86,9 @@ const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_ma | |||
| 85 | {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, | 86 | {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, |
| 86 | }; | 87 | }; |
| 87 | 88 | ||
| 88 | const std::map<bool, QString> Config::use_docked_mode_texts_map = { | 89 | const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = { |
| 89 | {true, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))}, | 90 | {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))}, |
| 90 | {false, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))}, | 91 | {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))}, |
| 91 | }; | 92 | }; |
| 92 | 93 | ||
| 93 | const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = { | 94 | const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = { |
| @@ -376,7 +377,7 @@ void Config::ReadControlValues() { | |||
| 376 | const auto controller_type = Settings::values.players.GetValue()[0].controller_type; | 377 | const auto controller_type = Settings::values.players.GetValue()[0].controller_type; |
| 377 | if (controller_type == Settings::ControllerType::Handheld) { | 378 | if (controller_type == Settings::ControllerType::Handheld) { |
| 378 | Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig()); | 379 | Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig()); |
| 379 | Settings::values.use_docked_mode.SetValue(false); | 380 | Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld); |
| 380 | } | 381 | } |
| 381 | 382 | ||
| 382 | if (IsCustomConfig()) { | 383 | if (IsCustomConfig()) { |
| @@ -592,8 +593,7 @@ void Config::ReadRendererValues() { | |||
| 592 | void Config::ReadScreenshotValues() { | 593 | void Config::ReadScreenshotValues() { |
| 593 | qt_config->beginGroup(QStringLiteral("Screenshots")); | 594 | qt_config->beginGroup(QStringLiteral("Screenshots")); |
| 594 | 595 | ||
| 595 | UISettings::values.enable_screenshot_save_as = | 596 | ReadCategory(Settings::Category::Screenshots); |
| 596 | ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); | ||
| 597 | FS::SetYuzuPath( | 597 | FS::SetYuzuPath( |
| 598 | FS::YuzuPath::ScreenshotsDir, | 598 | FS::YuzuPath::ScreenshotsDir, |
| 599 | qt_config | 599 | qt_config |
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 0ac74c8e7..727feebfb 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include <QMetaType> | 9 | #include <QMetaType> |
| 10 | #include <QVariant> | 10 | #include <QVariant> |
| 11 | #include "common/settings.h" | 11 | #include "common/settings.h" |
| 12 | #include "common/settings_enums.h" | ||
| 12 | #include "yuzu/uisettings.h" | 13 | #include "yuzu/uisettings.h" |
| 13 | 14 | ||
| 14 | class QSettings; | 15 | class QSettings; |
| @@ -51,7 +52,7 @@ public: | |||
| 51 | 52 | ||
| 52 | static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map; | 53 | static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map; |
| 53 | static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map; | 54 | static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map; |
| 54 | static const std::map<bool, QString> use_docked_mode_texts_map; | 55 | static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map; |
| 55 | static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map; | 56 | static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map; |
| 56 | static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map; | 57 | static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map; |
| 57 | static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map; | 58 | static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map; |
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index cbeb8f168..b22fda746 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp | |||
| @@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() { | |||
| 59 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); | 59 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); |
| 60 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); | 60 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); |
| 61 | ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); | 61 | ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); |
| 62 | ui->enable_renderdoc_hotkey->setEnabled(runtime_lock); | ||
| 63 | ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue()); | ||
| 62 | ui->enable_graphics_debugging->setEnabled(runtime_lock); | 64 | ui->enable_graphics_debugging->setEnabled(runtime_lock); |
| 63 | ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); | 65 | ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); |
| 64 | ui->enable_shader_feedback->setEnabled(runtime_lock); | 66 | ui->enable_shader_feedback->setEnabled(runtime_lock); |
| @@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() { | |||
| 111 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); | 113 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); |
| 112 | Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); | 114 | Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); |
| 113 | Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); | 115 | Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); |
| 116 | Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked(); | ||
| 114 | Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); | 117 | Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); |
| 115 | Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); | 118 | Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); |
| 116 | Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); | 119 | Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); |
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 97c7d9022..66b8b7459 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui | |||
| @@ -18,8 +18,8 @@ | |||
| 18 | <rect> | 18 | <rect> |
| 19 | <x>0</x> | 19 | <x>0</x> |
| 20 | <y>0</y> | 20 | <y>0</y> |
| 21 | <width>829</width> | 21 | <width>842</width> |
| 22 | <height>758</height> | 22 | <height>741</height> |
| 23 | </rect> | 23 | </rect> |
| 24 | </property> | 24 | </property> |
| 25 | <layout class="QVBoxLayout" name="verticalLayout_1"> | 25 | <layout class="QVBoxLayout" name="verticalLayout_1"> |
| @@ -260,7 +260,7 @@ | |||
| 260 | <string>Graphics</string> | 260 | <string>Graphics</string> |
| 261 | </property> | 261 | </property> |
| 262 | <layout class="QGridLayout" name="gridLayout_2"> | 262 | <layout class="QGridLayout" name="gridLayout_2"> |
| 263 | <item row="3" column="0"> | 263 | <item row="4" column="0"> |
| 264 | <widget class="QCheckBox" name="disable_loop_safety_checks"> | 264 | <widget class="QCheckBox" name="disable_loop_safety_checks"> |
| 265 | <property name="toolTip"> | 265 | <property name="toolTip"> |
| 266 | <string>When checked, it executes shaders without loop logic changes</string> | 266 | <string>When checked, it executes shaders without loop logic changes</string> |
| @@ -270,33 +270,53 @@ | |||
| 270 | </property> | 270 | </property> |
| 271 | </widget> | 271 | </widget> |
| 272 | </item> | 272 | </item> |
| 273 | <item row="4" column="0"> | 273 | <item row="8" column="0"> |
| 274 | <widget class="QCheckBox" name="dump_shaders"> | 274 | <widget class="QCheckBox" name="disable_macro_hle"> |
| 275 | <property name="enabled"> | 275 | <property name="enabled"> |
| 276 | <bool>true</bool> | 276 | <bool>true</bool> |
| 277 | </property> | 277 | </property> |
| 278 | <property name="toolTip"> | 278 | <property name="toolTip"> |
| 279 | <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> | 279 | <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> |
| 280 | </property> | 280 | </property> |
| 281 | <property name="text"> | 281 | <property name="text"> |
| 282 | <string>Dump Game Shaders</string> | 282 | <string>Disable Macro HLE</string> |
| 283 | </property> | 283 | </property> |
| 284 | </widget> | 284 | </widget> |
| 285 | </item> | 285 | </item> |
| 286 | <item row="7" column="0"> | 286 | <item row="7" column="0"> |
| 287 | <widget class="QCheckBox" name="disable_macro_hle"> | 287 | <widget class="QCheckBox" name="dump_macros"> |
| 288 | <property name="enabled"> | 288 | <property name="enabled"> |
| 289 | <bool>true</bool> | 289 | <bool>true</bool> |
| 290 | </property> | 290 | </property> |
| 291 | <property name="toolTip"> | 291 | <property name="toolTip"> |
| 292 | <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> | 292 | <string>When checked, it will dump all the macro programs of the GPU</string> |
| 293 | </property> | 293 | </property> |
| 294 | <property name="text"> | 294 | <property name="text"> |
| 295 | <string>Disable Macro HLE</string> | 295 | <string>Dump Maxwell Macros</string> |
| 296 | </property> | 296 | </property> |
| 297 | </widget> | 297 | </widget> |
| 298 | </item> | 298 | </item> |
| 299 | <item row="5" column="0"> | 299 | <item row="3" column="0"> |
| 300 | <widget class="QCheckBox" name="enable_nsight_aftermath"> | ||
| 301 | <property name="toolTip"> | ||
| 302 | <string>When checked, it enables Nsight Aftermath crash dumps</string> | ||
| 303 | </property> | ||
| 304 | <property name="text"> | ||
| 305 | <string>Enable Nsight Aftermath</string> | ||
| 306 | </property> | ||
| 307 | </widget> | ||
| 308 | </item> | ||
| 309 | <item row="2" column="0"> | ||
| 310 | <widget class="QCheckBox" name="enable_shader_feedback"> | ||
| 311 | <property name="toolTip"> | ||
| 312 | <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> | ||
| 313 | </property> | ||
| 314 | <property name="text"> | ||
| 315 | <string>Enable Shader Feedback</string> | ||
| 316 | </property> | ||
| 317 | </widget> | ||
| 318 | </item> | ||
| 319 | <item row="6" column="0"> | ||
| 300 | <widget class="QCheckBox" name="disable_macro_jit"> | 320 | <widget class="QCheckBox" name="disable_macro_jit"> |
| 301 | <property name="enabled"> | 321 | <property name="enabled"> |
| 302 | <bool>true</bool> | 322 | <bool>true</bool> |
| @@ -309,6 +329,22 @@ | |||
| 309 | </property> | 329 | </property> |
| 310 | </widget> | 330 | </widget> |
| 311 | </item> | 331 | </item> |
| 332 | <item row="9" column="0"> | ||
| 333 | <spacer name="verticalSpacer_5"> | ||
| 334 | <property name="orientation"> | ||
| 335 | <enum>Qt::Vertical</enum> | ||
| 336 | </property> | ||
| 337 | <property name="sizeType"> | ||
| 338 | <enum>QSizePolicy::Preferred</enum> | ||
| 339 | </property> | ||
| 340 | <property name="sizeHint" stdset="0"> | ||
| 341 | <size> | ||
| 342 | <width>20</width> | ||
| 343 | <height>0</height> | ||
| 344 | </size> | ||
| 345 | </property> | ||
| 346 | </spacer> | ||
| 347 | </item> | ||
| 312 | <item row="0" column="0"> | 348 | <item row="0" column="0"> |
| 313 | <widget class="QCheckBox" name="enable_graphics_debugging"> | 349 | <widget class="QCheckBox" name="enable_graphics_debugging"> |
| 314 | <property name="enabled"> | 350 | <property name="enabled"> |
| @@ -322,55 +358,26 @@ | |||
| 322 | </property> | 358 | </property> |
| 323 | </widget> | 359 | </widget> |
| 324 | </item> | 360 | </item> |
| 325 | <item row="6" column="0"> | 361 | <item row="5" column="0"> |
| 326 | <widget class="QCheckBox" name="dump_macros"> | 362 | <widget class="QCheckBox" name="dump_shaders"> |
| 327 | <property name="enabled"> | 363 | <property name="enabled"> |
| 328 | <bool>true</bool> | 364 | <bool>true</bool> |
| 329 | </property> | 365 | </property> |
| 330 | <property name="toolTip"> | 366 | <property name="toolTip"> |
| 331 | <string>When checked, it will dump all the macro programs of the GPU</string> | 367 | <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> |
| 332 | </property> | 368 | </property> |
| 333 | <property name="text"> | 369 | <property name="text"> |
| 334 | <string>Dump Maxwell Macros</string> | 370 | <string>Dump Game Shaders</string> |
| 335 | </property> | 371 | </property> |
| 336 | </widget> | 372 | </widget> |
| 337 | </item> | 373 | </item> |
| 338 | <item row="1" column="0"> | 374 | <item row="1" column="0"> |
| 339 | <widget class="QCheckBox" name="enable_shader_feedback"> | 375 | <widget class="QCheckBox" name="enable_renderdoc_hotkey"> |
| 340 | <property name="toolTip"> | ||
| 341 | <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> | ||
| 342 | </property> | ||
| 343 | <property name="text"> | ||
| 344 | <string>Enable Shader Feedback</string> | ||
| 345 | </property> | ||
| 346 | </widget> | ||
| 347 | </item> | ||
| 348 | <item row="2" column="0"> | ||
| 349 | <widget class="QCheckBox" name="enable_nsight_aftermath"> | ||
| 350 | <property name="toolTip"> | ||
| 351 | <string>When checked, it enables Nsight Aftermath crash dumps</string> | ||
| 352 | </property> | ||
| 353 | <property name="text"> | 376 | <property name="text"> |
| 354 | <string>Enable Nsight Aftermath</string> | 377 | <string>Enable Renderdoc Hotkey</string> |
| 355 | </property> | 378 | </property> |
| 356 | </widget> | 379 | </widget> |
| 357 | </item> | 380 | </item> |
| 358 | <item row="8" column="0"> | ||
| 359 | <spacer name="verticalSpacer_5"> | ||
| 360 | <property name="orientation"> | ||
| 361 | <enum>Qt::Vertical</enum> | ||
| 362 | </property> | ||
| 363 | <property name="sizeType"> | ||
| 364 | <enum>QSizePolicy::Preferred</enum> | ||
| 365 | </property> | ||
| 366 | <property name="sizeHint" stdset="0"> | ||
| 367 | <size> | ||
| 368 | <width>20</width> | ||
| 369 | <height>0</height> | ||
| 370 | </size> | ||
| 371 | </property> | ||
| 372 | </spacer> | ||
| 373 | </item> | ||
| 374 | </layout> | 381 | </layout> |
| 375 | </widget> | 382 | </widget> |
| 376 | </item> | 383 | </item> |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 3c6bb3eb1..0ad95cc02 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include <memory> | 4 | #include <memory> |
| 5 | #include "common/logging/log.h" | 5 | #include "common/logging/log.h" |
| 6 | #include "common/settings.h" | 6 | #include "common/settings.h" |
| 7 | #include "common/settings_enums.h" | ||
| 7 | #include "core/core.h" | 8 | #include "core/core.h" |
| 8 | #include "ui_configure.h" | 9 | #include "ui_configure.h" |
| 9 | #include "vk_device_info.h" | 10 | #include "vk_device_info.h" |
| @@ -41,16 +42,19 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, | |||
| 41 | general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)}, | 42 | general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)}, |
| 42 | graphics_advanced_tab{ | 43 | graphics_advanced_tab{ |
| 43 | std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)}, | 44 | std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)}, |
| 45 | ui_tab{std::make_unique<ConfigureUi>(system_, this)}, | ||
| 44 | graphics_tab{std::make_unique<ConfigureGraphics>( | 46 | graphics_tab{std::make_unique<ConfigureGraphics>( |
| 45 | system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, | 47 | system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, |
| 48 | [this](Settings::AspectRatio ratio, Settings::ResolutionSetup setup) { | ||
| 49 | ui_tab->UpdateScreenshotInfo(ratio, setup); | ||
| 50 | }, | ||
| 46 | nullptr, *builder, this)}, | 51 | nullptr, *builder, this)}, |
| 47 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, | 52 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, |
| 48 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, | 53 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, |
| 49 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, | 54 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, |
| 50 | profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, | 55 | profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, |
| 51 | system_tab{std::make_unique<ConfigureSystem>(system_, nullptr, *builder, this)}, | 56 | system_tab{std::make_unique<ConfigureSystem>(system_, nullptr, *builder, this)}, |
| 52 | ui_tab{std::make_unique<ConfigureUi>(system_, this)}, web_tab{std::make_unique<ConfigureWeb>( | 57 | web_tab{std::make_unique<ConfigureWeb>(this)} { |
| 53 | this)} { | ||
| 54 | Settings::SetConfiguringGlobal(true); | 58 | Settings::SetConfiguringGlobal(true); |
| 55 | 59 | ||
| 56 | ui->setupUi(this); | 60 | ui->setupUi(this); |
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 96e9a8c3e..b28ce288c 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h | |||
| @@ -81,12 +81,12 @@ private: | |||
| 81 | std::unique_ptr<ConfigureFilesystem> filesystem_tab; | 81 | std::unique_ptr<ConfigureFilesystem> filesystem_tab; |
| 82 | std::unique_ptr<ConfigureGeneral> general_tab; | 82 | std::unique_ptr<ConfigureGeneral> general_tab; |
| 83 | std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab; | 83 | std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab; |
| 84 | std::unique_ptr<ConfigureUi> ui_tab; | ||
| 84 | std::unique_ptr<ConfigureGraphics> graphics_tab; | 85 | std::unique_ptr<ConfigureGraphics> graphics_tab; |
| 85 | std::unique_ptr<ConfigureHotkeys> hotkeys_tab; | 86 | std::unique_ptr<ConfigureHotkeys> hotkeys_tab; |
| 86 | std::unique_ptr<ConfigureInput> input_tab; | 87 | std::unique_ptr<ConfigureInput> input_tab; |
| 87 | std::unique_ptr<ConfigureNetwork> network_tab; | 88 | std::unique_ptr<ConfigureNetwork> network_tab; |
| 88 | std::unique_ptr<ConfigureProfileManager> profile_tab; | 89 | std::unique_ptr<ConfigureProfileManager> profile_tab; |
| 89 | std::unique_ptr<ConfigureSystem> system_tab; | 90 | std::unique_ptr<ConfigureSystem> system_tab; |
| 90 | std::unique_ptr<ConfigureUi> ui_tab; | ||
| 91 | std::unique_ptr<ConfigureWeb> web_tab; | 91 | std::unique_ptr<ConfigureWeb> web_tab; |
| 92 | }; | 92 | }; |
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index a94fbc89a..fd6bebf0f 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #include <QtCore/qobjectdefs.h> | 24 | #include <QtCore/qobjectdefs.h> |
| 25 | #include <qabstractbutton.h> | 25 | #include <qabstractbutton.h> |
| 26 | #include <qboxlayout.h> | 26 | #include <qboxlayout.h> |
| 27 | #include <qcombobox.h> | ||
| 27 | #include <qcoreevent.h> | 28 | #include <qcoreevent.h> |
| 28 | #include <qglobal.h> | 29 | #include <qglobal.h> |
| 29 | #include <qgridlayout.h> | 30 | #include <qgridlayout.h> |
| @@ -77,13 +78,16 @@ static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) | |||
| 77 | } | 78 | } |
| 78 | } | 79 | } |
| 79 | 80 | ||
| 80 | ConfigureGraphics::ConfigureGraphics(const Core::System& system_, | 81 | ConfigureGraphics::ConfigureGraphics( |
| 81 | std::vector<VkDeviceInfo::Record>& records_, | 82 | const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_, |
| 82 | const std::function<void()>& expose_compute_option_, | 83 | const std::function<void()>& expose_compute_option_, |
| 83 | std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_, | 84 | const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>& |
| 84 | const ConfigurationShared::Builder& builder, QWidget* parent) | 85 | update_aspect_ratio_, |
| 86 | std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_, | ||
| 87 | const ConfigurationShared::Builder& builder, QWidget* parent) | ||
| 85 | : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, | 88 | : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, |
| 86 | records{records_}, expose_compute_option{expose_compute_option_}, system{system_}, | 89 | records{records_}, expose_compute_option{expose_compute_option_}, |
| 90 | update_aspect_ratio{update_aspect_ratio_}, system{system_}, | ||
| 87 | combobox_translations{builder.ComboboxTranslations()}, | 91 | combobox_translations{builder.ComboboxTranslations()}, |
| 88 | shader_mapping{ | 92 | shader_mapping{ |
| 89 | combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} { | 93 | combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} { |
| @@ -140,6 +144,26 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, | |||
| 140 | UpdateBackgroundColorButton(new_bg_color); | 144 | UpdateBackgroundColorButton(new_bg_color); |
| 141 | }); | 145 | }); |
| 142 | 146 | ||
| 147 | const auto& update_screenshot_info = [this, &builder]() { | ||
| 148 | const auto& combobox_enumerations = builder.ComboboxTranslations().at( | ||
| 149 | Settings::EnumMetadata<Settings::AspectRatio>::Index()); | ||
| 150 | const auto index = aspect_ratio_combobox->currentIndex(); | ||
| 151 | const auto ratio = static_cast<Settings::AspectRatio>(combobox_enumerations[index].first); | ||
| 152 | |||
| 153 | const auto& combobox_enumerations_resolution = builder.ComboboxTranslations().at( | ||
| 154 | Settings::EnumMetadata<Settings::ResolutionSetup>::Index()); | ||
| 155 | const auto res_index = resolution_combobox->currentIndex(); | ||
| 156 | const auto setup = static_cast<Settings::ResolutionSetup>( | ||
| 157 | combobox_enumerations_resolution[res_index].first); | ||
| 158 | |||
| 159 | update_aspect_ratio(ratio, setup); | ||
| 160 | }; | ||
| 161 | |||
| 162 | connect(aspect_ratio_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), | ||
| 163 | update_screenshot_info); | ||
| 164 | connect(resolution_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), | ||
| 165 | update_screenshot_info); | ||
| 166 | |||
| 143 | api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled()); | 167 | api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled()); |
| 144 | ui->api_widget->setEnabled( | 168 | ui->api_widget->setEnabled( |
| 145 | (!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) && | 169 | (!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) && |
| @@ -169,14 +193,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() { | |||
| 169 | : vsync_mode_combobox_enum_map[current_index]; | 193 | : vsync_mode_combobox_enum_map[current_index]; |
| 170 | int index{}; | 194 | int index{}; |
| 171 | const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device | 195 | const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device |
| 172 | if (device == -1) { | ||
| 173 | // Invalid device | ||
| 174 | return; | ||
| 175 | } | ||
| 176 | 196 | ||
| 177 | const auto& present_modes = //< relevant vector of present modes for the selected device or API | 197 | const auto& present_modes = //< relevant vector of present modes for the selected device or API |
| 178 | backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] | 198 | backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device] |
| 179 | : default_present_modes; | 199 | : default_present_modes; |
| 180 | 200 | ||
| 181 | vsync_mode_combobox->clear(); | 201 | vsync_mode_combobox->clear(); |
| 182 | vsync_mode_combobox_enum_map.clear(); | 202 | vsync_mode_combobox_enum_map.clear(); |
| @@ -280,6 +300,14 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { | |||
| 280 | // Keep track of vsync_mode's combobox so we can populate it | 300 | // Keep track of vsync_mode's combobox so we can populate it |
| 281 | vsync_mode_combobox = widget->combobox; | 301 | vsync_mode_combobox = widget->combobox; |
| 282 | hold_graphics.emplace(setting->Id(), widget); | 302 | hold_graphics.emplace(setting->Id(), widget); |
| 303 | } else if (setting->Id() == Settings::values.aspect_ratio.Id()) { | ||
| 304 | // Keep track of the aspect ratio combobox to update other UI tabs that need it | ||
| 305 | aspect_ratio_combobox = widget->combobox; | ||
| 306 | hold_graphics.emplace(setting->Id(), widget); | ||
| 307 | } else if (setting->Id() == Settings::values.resolution_setup.Id()) { | ||
| 308 | // Keep track of the resolution combobox to update other UI tabs that need it | ||
| 309 | resolution_combobox = widget->combobox; | ||
| 310 | hold_graphics.emplace(setting->Id(), widget); | ||
| 283 | } else { | 311 | } else { |
| 284 | hold_graphics.emplace(setting->Id(), widget); | 312 | hold_graphics.emplace(setting->Id(), widget); |
| 285 | } | 313 | } |
| @@ -465,11 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() { | |||
| 465 | } | 493 | } |
| 466 | 494 | ||
| 467 | Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { | 495 | Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { |
| 468 | if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { | 496 | const auto selected_backend = [&]() { |
| 469 | return Settings::values.renderer_backend.GetValue(true); | 497 | if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { |
| 498 | return Settings::values.renderer_backend.GetValue(true); | ||
| 499 | } | ||
| 500 | return static_cast<Settings::RendererBackend>( | ||
| 501 | combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index()) | ||
| 502 | .at(api_combobox->currentIndex()) | ||
| 503 | .first); | ||
| 504 | }(); | ||
| 505 | |||
| 506 | if (selected_backend == Settings::RendererBackend::Vulkan && | ||
| 507 | UISettings::values.has_broken_vulkan) { | ||
| 508 | return Settings::RendererBackend::OpenGL; | ||
| 470 | } | 509 | } |
| 471 | return static_cast<Settings::RendererBackend>( | 510 | return selected_backend; |
| 472 | combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index()) | ||
| 473 | .at(api_combobox->currentIndex()) | ||
| 474 | .first); | ||
| 475 | } | 511 | } |
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 02d9b00f1..9c24a56db 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | #include <qobjectdefs.h> | 14 | #include <qobjectdefs.h> |
| 15 | #include <vulkan/vulkan_core.h> | 15 | #include <vulkan/vulkan_core.h> |
| 16 | #include "common/common_types.h" | 16 | #include "common/common_types.h" |
| 17 | #include "common/settings_enums.h" | ||
| 17 | #include "configuration/shared_translation.h" | 18 | #include "configuration/shared_translation.h" |
| 18 | #include "vk_device_info.h" | 19 | #include "vk_device_info.h" |
| 19 | #include "yuzu/configuration/configuration_shared.h" | 20 | #include "yuzu/configuration/configuration_shared.h" |
| @@ -43,12 +44,13 @@ class Builder; | |||
| 43 | 44 | ||
| 44 | class ConfigureGraphics : public ConfigurationShared::Tab { | 45 | class ConfigureGraphics : public ConfigurationShared::Tab { |
| 45 | public: | 46 | public: |
| 46 | explicit ConfigureGraphics(const Core::System& system_, | 47 | explicit ConfigureGraphics( |
| 47 | std::vector<VkDeviceInfo::Record>& records, | 48 | const Core::System& system_, std::vector<VkDeviceInfo::Record>& records, |
| 48 | const std::function<void()>& expose_compute_option_, | 49 | const std::function<void()>& expose_compute_option, |
| 49 | std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group, | 50 | const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>& |
| 50 | const ConfigurationShared::Builder& builder, | 51 | update_aspect_ratio, |
| 51 | QWidget* parent = nullptr); | 52 | std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group, |
| 53 | const ConfigurationShared::Builder& builder, QWidget* parent = nullptr); | ||
| 52 | ~ConfigureGraphics() override; | 54 | ~ConfigureGraphics() override; |
| 53 | 55 | ||
| 54 | void ApplyConfiguration() override; | 56 | void ApplyConfiguration() override; |
| @@ -91,6 +93,7 @@ private: | |||
| 91 | u32 vulkan_device{}; | 93 | u32 vulkan_device{}; |
| 92 | Settings::ShaderBackend shader_backend{}; | 94 | Settings::ShaderBackend shader_backend{}; |
| 93 | const std::function<void()>& expose_compute_option; | 95 | const std::function<void()>& expose_compute_option; |
| 96 | const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)> update_aspect_ratio; | ||
| 94 | 97 | ||
| 95 | const Core::System& system; | 98 | const Core::System& system; |
| 96 | const ConfigurationShared::ComboboxTranslationMap& combobox_translations; | 99 | const ConfigurationShared::ComboboxTranslationMap& combobox_translations; |
| @@ -104,4 +107,6 @@ private: | |||
| 104 | QWidget* vulkan_device_widget; | 107 | QWidget* vulkan_device_widget; |
| 105 | QWidget* api_widget; | 108 | QWidget* api_widget; |
| 106 | QWidget* shader_backend_widget; | 109 | QWidget* shader_backend_widget; |
| 110 | QComboBox* aspect_ratio_combobox; | ||
| 111 | QComboBox* resolution_combobox; | ||
| 107 | }; | 112 | }; |
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 7fce85bca..e8f9ebfd8 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp | |||
| @@ -4,6 +4,8 @@ | |||
| 4 | #include <memory> | 4 | #include <memory> |
| 5 | #include <thread> | 5 | #include <thread> |
| 6 | 6 | ||
| 7 | #include "common/settings.h" | ||
| 8 | #include "common/settings_enums.h" | ||
| 7 | #include "core/core.h" | 9 | #include "core/core.h" |
| 8 | #include "core/hid/emulated_controller.h" | 10 | #include "core/hid/emulated_controller.h" |
| 9 | #include "core/hid/hid_core.h" | 11 | #include "core/hid/hid_core.h" |
| @@ -197,9 +199,11 @@ void ConfigureInput::ApplyConfiguration() { | |||
| 197 | 199 | ||
| 198 | advanced->ApplyConfiguration(); | 200 | advanced->ApplyConfiguration(); |
| 199 | 201 | ||
| 200 | const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); | 202 | const bool pre_docked_mode = Settings::IsDockedMode(); |
| 201 | Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); | 203 | const bool docked_mode_selected = ui->radioDocked->isChecked(); |
| 202 | OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system); | 204 | Settings::values.use_docked_mode.SetValue( |
| 205 | docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld); | ||
| 206 | OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system); | ||
| 203 | 207 | ||
| 204 | Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); | 208 | Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); |
| 205 | Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); | 209 | Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); |
| @@ -267,8 +271,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) { | |||
| 267 | ui->radioDocked->setEnabled(!is_handheld); | 271 | ui->radioDocked->setEnabled(!is_handheld); |
| 268 | ui->radioUndocked->setEnabled(!is_handheld); | 272 | ui->radioUndocked->setEnabled(!is_handheld); |
| 269 | 273 | ||
| 270 | ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); | 274 | ui->radioDocked->setChecked(Settings::IsDockedMode()); |
| 271 | ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); | 275 | ui->radioUndocked->setChecked(!Settings::IsDockedMode()); |
| 272 | 276 | ||
| 273 | // Also force into undocked mode if the controller type is handheld. | 277 | // Also force into undocked mode if the controller type is handheld. |
| 274 | if (is_handheld) { | 278 | if (is_handheld) { |
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index cd8b3012e..b91d6ad4a 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp | |||
| @@ -17,6 +17,8 @@ | |||
| 17 | #include <QTimer> | 17 | #include <QTimer> |
| 18 | 18 | ||
| 19 | #include "common/fs/fs_util.h" | 19 | #include "common/fs/fs_util.h" |
| 20 | #include "common/settings_enums.h" | ||
| 21 | #include "common/settings_input.h" | ||
| 20 | #include "configuration/shared_widget.h" | 22 | #include "configuration/shared_widget.h" |
| 21 | #include "core/core.h" | 23 | #include "core/core.h" |
| 22 | #include "core/file_sys/control_metadata.h" | 24 | #include "core/file_sys/control_metadata.h" |
| @@ -57,7 +59,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st | |||
| 57 | std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this); | 59 | std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this); |
| 58 | graphics_tab = std::make_unique<ConfigureGraphics>( | 60 | graphics_tab = std::make_unique<ConfigureGraphics>( |
| 59 | system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, | 61 | system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, |
| 60 | tab_group, *builder, this); | 62 | [](Settings::AspectRatio, Settings::ResolutionSetup) {}, tab_group, *builder, this); |
| 61 | input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); | 63 | input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); |
| 62 | system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this); | 64 | system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this); |
| 63 | 65 | ||
| @@ -97,6 +99,12 @@ void ConfigurePerGame::ApplyConfiguration() { | |||
| 97 | addons_tab->ApplyConfiguration(); | 99 | addons_tab->ApplyConfiguration(); |
| 98 | input_tab->ApplyConfiguration(); | 100 | input_tab->ApplyConfiguration(); |
| 99 | 101 | ||
| 102 | if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type == | ||
| 103 | Settings::ControllerType::Handheld) { | ||
| 104 | Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld); | ||
| 105 | Settings::values.use_docked_mode.SetGlobal(true); | ||
| 106 | } | ||
| 107 | |||
| 100 | system.ApplySettings(); | 108 | system.ApplySettings(); |
| 101 | Settings::LogSettings(); | 109 | Settings::LogSettings(); |
| 102 | 110 | ||
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index c4833f4e7..0c8e5c8b4 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp | |||
| @@ -106,6 +106,11 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) { | |||
| 106 | push(Settings::values.linkage.by_category[Settings::Category::System]); | 106 | push(Settings::values.linkage.by_category[Settings::Category::System]); |
| 107 | 107 | ||
| 108 | for (auto setting : settings) { | 108 | for (auto setting : settings) { |
| 109 | if (setting->Id() == Settings::values.use_docked_mode.Id() && | ||
| 110 | Settings::IsConfiguringGlobal()) { | ||
| 111 | continue; | ||
| 112 | } | ||
| 113 | |||
| 109 | ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs); | 114 | ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs); |
| 110 | 115 | ||
| 111 | if (widget == nullptr) { | 116 | if (widget == nullptr) { |
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 2ebb80302..a9fde9f4f 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -1,18 +1,32 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2016 Citra Emulator Project | 1 | // SPDX-FileCopyrightText: 2016 Citra Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "yuzu/configuration/configure_ui.h" | ||
| 5 | |||
| 4 | #include <array> | 6 | #include <array> |
| 7 | #include <cstdlib> | ||
| 8 | #include <set> | ||
| 9 | #include <stdexcept> | ||
| 10 | #include <string> | ||
| 5 | #include <utility> | 11 | #include <utility> |
| 6 | #include <QFileDialog> | ||
| 7 | 12 | ||
| 13 | #include <QCheckBox> | ||
| 14 | #include <QComboBox> | ||
| 15 | #include <QCoreApplication> | ||
| 8 | #include <QDirIterator> | 16 | #include <QDirIterator> |
| 17 | #include <QFileDialog> | ||
| 18 | #include <QString> | ||
| 19 | #include <QToolButton> | ||
| 20 | #include <QVariant> | ||
| 21 | |||
| 9 | #include "common/common_types.h" | 22 | #include "common/common_types.h" |
| 10 | #include "common/fs/path_util.h" | 23 | #include "common/fs/path_util.h" |
| 11 | #include "common/logging/log.h" | 24 | #include "common/logging/log.h" |
| 12 | #include "common/settings.h" | 25 | #include "common/settings.h" |
| 26 | #include "common/settings_enums.h" | ||
| 13 | #include "core/core.h" | 27 | #include "core/core.h" |
| 28 | #include "core/frontend/framebuffer_layout.h" | ||
| 14 | #include "ui_configure_ui.h" | 29 | #include "ui_configure_ui.h" |
| 15 | #include "yuzu/configuration/configure_ui.h" | ||
| 16 | #include "yuzu/uisettings.h" | 30 | #include "yuzu/uisettings.h" |
| 17 | 31 | ||
| 18 | namespace { | 32 | namespace { |
| @@ -54,8 +68,40 @@ QString GetTranslatedRowTextName(size_t index) { | |||
| 54 | } | 68 | } |
| 55 | } // Anonymous namespace | 69 | } // Anonymous namespace |
| 56 | 70 | ||
| 71 | static float GetUpFactor(Settings::ResolutionSetup res_setup) { | ||
| 72 | Settings::ResolutionScalingInfo info{}; | ||
| 73 | Settings::TranslateResolutionInfo(res_setup, info); | ||
| 74 | return info.up_factor; | ||
| 75 | } | ||
| 76 | |||
| 77 | static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* parent) { | ||
| 78 | screenshot_height->clear(); | ||
| 79 | |||
| 80 | const auto& enumeration = | ||
| 81 | Settings::EnumMetadata<Settings::ResolutionSetup>::Canonicalizations(); | ||
| 82 | std::set<u32> resolutions{}; | ||
| 83 | for (const auto& [name, value] : enumeration) { | ||
| 84 | const float up_factor = GetUpFactor(value); | ||
| 85 | u32 height_undocked = Layout::ScreenUndocked::Height * up_factor; | ||
| 86 | u32 height_docked = Layout::ScreenDocked::Height * up_factor; | ||
| 87 | resolutions.emplace(height_undocked); | ||
| 88 | resolutions.emplace(height_docked); | ||
| 89 | } | ||
| 90 | |||
| 91 | screenshot_height->addItem(parent->tr("Auto", "Screenshot height option")); | ||
| 92 | for (const auto res : resolutions) { | ||
| 93 | screenshot_height->addItem(QString::fromStdString(std::to_string(res))); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | static u32 ScreenshotDimensionToInt(const QString& height) { | ||
| 98 | return std::strtoul(height.toUtf8(), nullptr, 0); | ||
| 99 | } | ||
| 100 | |||
| 57 | ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | 101 | ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) |
| 58 | : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()}, system{system_} { | 102 | : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()}, |
| 103 | ratio{Settings::values.aspect_ratio.GetValue()}, | ||
| 104 | resolution_setting{Settings::values.resolution_setup.GetValue()}, system{system_} { | ||
| 59 | ui->setupUi(this); | 105 | ui->setupUi(this); |
| 60 | 106 | ||
| 61 | InitializeLanguageComboBox(); | 107 | InitializeLanguageComboBox(); |
| @@ -68,6 +114,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | |||
| 68 | InitializeIconSizeComboBox(); | 114 | InitializeIconSizeComboBox(); |
| 69 | InitializeRowComboBoxes(); | 115 | InitializeRowComboBoxes(); |
| 70 | 116 | ||
| 117 | PopulateResolutionComboBox(ui->screenshot_height, this); | ||
| 118 | |||
| 71 | SetConfiguration(); | 119 | SetConfiguration(); |
| 72 | 120 | ||
| 73 | // Force game list reload if any of the relevant settings are changed. | 121 | // Force game list reload if any of the relevant settings are changed. |
| @@ -104,6 +152,10 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | |||
| 104 | ui->screenshot_path_edit->setText(dir); | 152 | ui->screenshot_path_edit->setText(dir); |
| 105 | } | 153 | } |
| 106 | }); | 154 | }); |
| 155 | |||
| 156 | connect(ui->screenshot_height, &QComboBox::currentTextChanged, [this]() { UpdateWidthText(); }); | ||
| 157 | |||
| 158 | UpdateWidthText(); | ||
| 107 | } | 159 | } |
| 108 | 160 | ||
| 109 | ConfigureUi::~ConfigureUi() = default; | 161 | ConfigureUi::~ConfigureUi() = default; |
| @@ -123,6 +175,10 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 123 | UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); | 175 | UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); |
| 124 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir, | 176 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir, |
| 125 | ui->screenshot_path_edit->text().toStdString()); | 177 | ui->screenshot_path_edit->text().toStdString()); |
| 178 | |||
| 179 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); | ||
| 180 | UISettings::values.screenshot_height.SetValue(height); | ||
| 181 | |||
| 126 | system.ApplySettings(); | 182 | system.ApplySettings(); |
| 127 | } | 183 | } |
| 128 | 184 | ||
| @@ -147,6 +203,13 @@ void ConfigureUi::SetConfiguration() { | |||
| 147 | UISettings::values.enable_screenshot_save_as.GetValue()); | 203 | UISettings::values.enable_screenshot_save_as.GetValue()); |
| 148 | ui->screenshot_path_edit->setText(QString::fromStdString( | 204 | ui->screenshot_path_edit->setText(QString::fromStdString( |
| 149 | Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir))); | 205 | Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir))); |
| 206 | |||
| 207 | const auto height = UISettings::values.screenshot_height.GetValue(); | ||
| 208 | if (height == 0) { | ||
| 209 | ui->screenshot_height->setCurrentIndex(0); | ||
| 210 | } else { | ||
| 211 | ui->screenshot_height->setCurrentText(QStringLiteral("%1").arg(height)); | ||
| 212 | } | ||
| 150 | } | 213 | } |
| 151 | 214 | ||
| 152 | void ConfigureUi::changeEvent(QEvent* event) { | 215 | void ConfigureUi::changeEvent(QEvent* event) { |
| @@ -317,3 +380,29 @@ void ConfigureUi::OnLanguageChanged(int index) { | |||
| 317 | 380 | ||
| 318 | emit LanguageChanged(ui->language_combobox->itemData(index).toString()); | 381 | emit LanguageChanged(ui->language_combobox->itemData(index).toString()); |
| 319 | } | 382 | } |
| 383 | |||
| 384 | void ConfigureUi::UpdateWidthText() { | ||
| 385 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); | ||
| 386 | const u32 width = UISettings::CalculateWidth(height, ratio); | ||
| 387 | if (height == 0) { | ||
| 388 | const auto up_factor = GetUpFactor(resolution_setting); | ||
| 389 | const u32 height_docked = Layout::ScreenDocked::Height * up_factor; | ||
| 390 | const u32 width_docked = UISettings::CalculateWidth(height_docked, ratio); | ||
| 391 | const u32 height_undocked = Layout::ScreenUndocked::Height * up_factor; | ||
| 392 | const u32 width_undocked = UISettings::CalculateWidth(height_undocked, ratio); | ||
| 393 | ui->screenshot_width->setText(tr("Auto (%1 x %2, %3 x %4)", "Screenshot width value") | ||
| 394 | .arg(width_undocked) | ||
| 395 | .arg(height_undocked) | ||
| 396 | .arg(width_docked) | ||
| 397 | .arg(height_docked)); | ||
| 398 | } else { | ||
| 399 | ui->screenshot_width->setText(QStringLiteral("%1 x").arg(width)); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | void ConfigureUi::UpdateScreenshotInfo(Settings::AspectRatio ratio_, | ||
| 404 | Settings::ResolutionSetup resolution_setting_) { | ||
| 405 | ratio = ratio_; | ||
| 406 | resolution_setting = resolution_setting_; | ||
| 407 | UpdateWidthText(); | ||
| 408 | } | ||
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h index 95af8370e..2a2563a13 100644 --- a/src/yuzu/configuration/configure_ui.h +++ b/src/yuzu/configuration/configure_ui.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <QWidget> | 7 | #include <QWidget> |
| 8 | #include "common/settings_enums.h" | ||
| 8 | 9 | ||
| 9 | namespace Core { | 10 | namespace Core { |
| 10 | class System; | 11 | class System; |
| @@ -23,6 +24,9 @@ public: | |||
| 23 | 24 | ||
| 24 | void ApplyConfiguration(); | 25 | void ApplyConfiguration(); |
| 25 | 26 | ||
| 27 | void UpdateScreenshotInfo(Settings::AspectRatio ratio, | ||
| 28 | Settings::ResolutionSetup resolution_info); | ||
| 29 | |||
| 26 | private slots: | 30 | private slots: |
| 27 | void OnLanguageChanged(int index); | 31 | void OnLanguageChanged(int index); |
| 28 | 32 | ||
| @@ -44,7 +48,11 @@ private: | |||
| 44 | void UpdateFirstRowComboBox(bool init = false); | 48 | void UpdateFirstRowComboBox(bool init = false); |
| 45 | void UpdateSecondRowComboBox(bool init = false); | 49 | void UpdateSecondRowComboBox(bool init = false); |
| 46 | 50 | ||
| 51 | void UpdateWidthText(); | ||
| 52 | |||
| 47 | std::unique_ptr<Ui::ConfigureUi> ui; | 53 | std::unique_ptr<Ui::ConfigureUi> ui; |
| 48 | 54 | ||
| 55 | Settings::AspectRatio ratio; | ||
| 56 | Settings::ResolutionSetup resolution_setting; | ||
| 49 | Core::System& system; | 57 | Core::System& system; |
| 50 | }; | 58 | }; |
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index 10bb27312..cb66ef104 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | <x>0</x> | 7 | <x>0</x> |
| 8 | <y>0</y> | 8 | <y>0</y> |
| 9 | <width>363</width> | 9 | <width>363</width> |
| 10 | <height>562</height> | 10 | <height>603</height> |
| 11 | </rect> | 11 | </rect> |
| 12 | </property> | 12 | </property> |
| 13 | <property name="windowTitle"> | 13 | <property name="windowTitle"> |
| @@ -201,6 +201,41 @@ | |||
| 201 | </item> | 201 | </item> |
| 202 | </layout> | 202 | </layout> |
| 203 | </item> | 203 | </item> |
| 204 | <item> | ||
| 205 | <layout class="QGridLayout" name="gridLayout"> | ||
| 206 | <property name="spacing"> | ||
| 207 | <number>6</number> | ||
| 208 | </property> | ||
| 209 | <item row="0" column="1"> | ||
| 210 | <layout class="QHBoxLayout" name="horizontalLayout_5"> | ||
| 211 | <item> | ||
| 212 | <widget class="QLabel" name="screenshot_width"> | ||
| 213 | <property name="text"> | ||
| 214 | <string>TextLabel</string> | ||
| 215 | </property> | ||
| 216 | <property name="alignment"> | ||
| 217 | <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | ||
| 218 | </property> | ||
| 219 | </widget> | ||
| 220 | </item> | ||
| 221 | <item> | ||
| 222 | <widget class="QComboBox" name="screenshot_height"> | ||
| 223 | <property name="editable"> | ||
| 224 | <bool>true</bool> | ||
| 225 | </property> | ||
| 226 | </widget> | ||
| 227 | </item> | ||
| 228 | </layout> | ||
| 229 | </item> | ||
| 230 | <item row="0" column="0"> | ||
| 231 | <widget class="QLabel" name="label_3"> | ||
| 232 | <property name="text"> | ||
| 233 | <string>Resolution:</string> | ||
| 234 | </property> | ||
| 235 | </widget> | ||
| 236 | </item> | ||
| 237 | </layout> | ||
| 238 | </item> | ||
| 204 | </layout> | 239 | </layout> |
| 205 | </item> | 240 | </item> |
| 206 | </layout> | 241 | </layout> |
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index 335810788..276bdbaba 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp | |||
| @@ -135,7 +135,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) { | |||
| 135 | INSERT(Settings, region_index, "Region:", ""); | 135 | INSERT(Settings, region_index, "Region:", ""); |
| 136 | INSERT(Settings, time_zone_index, "Time Zone:", ""); | 136 | INSERT(Settings, time_zone_index, "Time Zone:", ""); |
| 137 | INSERT(Settings, sound_index, "Sound Output Mode:", ""); | 137 | INSERT(Settings, sound_index, "Sound Output Mode:", ""); |
| 138 | INSERT(Settings, use_docked_mode, "", ""); | 138 | INSERT(Settings, use_docked_mode, "Console Mode:", ""); |
| 139 | INSERT(Settings, current_user, "", ""); | 139 | INSERT(Settings, current_user, "", ""); |
| 140 | 140 | ||
| 141 | // Controls | 141 | // Controls |
| @@ -379,6 +379,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) { | |||
| 379 | PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"), | 379 | PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"), |
| 380 | PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"), | 380 | PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"), |
| 381 | }}); | 381 | }}); |
| 382 | translations->insert( | ||
| 383 | {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), | ||
| 384 | {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); | ||
| 382 | 385 | ||
| 383 | #undef PAIR | 386 | #undef PAIR |
| 384 | #undef CTX_PAIR | 387 | #undef CTX_PAIR |
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp index bdb38c8ea..ea8d7add4 100644 --- a/src/yuzu/configuration/shared_widget.cpp +++ b/src/yuzu/configuration/shared_widget.cpp | |||
| @@ -23,6 +23,7 @@ | |||
| 23 | #include <QLineEdit> | 23 | #include <QLineEdit> |
| 24 | #include <QObject> | 24 | #include <QObject> |
| 25 | #include <QPushButton> | 25 | #include <QPushButton> |
| 26 | #include <QRadioButton> | ||
| 26 | #include <QRegularExpression> | 27 | #include <QRegularExpression> |
| 27 | #include <QSizePolicy> | 28 | #include <QSizePolicy> |
| 28 | #include <QSlider> | 29 | #include <QSlider> |
| @@ -62,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) { | |||
| 62 | return tr("%", context.c_str()); | 63 | return tr("%", context.c_str()); |
| 63 | } | 64 | } |
| 64 | 65 | ||
| 65 | return QStringLiteral(""); | 66 | return default_suffix; |
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { | 69 | QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { |
| @@ -70,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren | |||
| 70 | 71 | ||
| 71 | QStyle* style = parent->style(); | 72 | QStyle* style = parent->style(); |
| 72 | QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); | 73 | QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); |
| 73 | QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); | 74 | QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent); |
| 74 | restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); | 75 | restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); |
| 75 | restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); | 76 | restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); |
| 76 | 77 | ||
| @@ -150,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer, | |||
| 150 | return -1; | 151 | return -1; |
| 151 | }; | 152 | }; |
| 152 | 153 | ||
| 153 | const u32 setting_value = std::stoi(setting.ToString()); | 154 | const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0); |
| 154 | combobox->setCurrentIndex(find_index(setting_value)); | 155 | combobox->setCurrentIndex(find_index(setting_value)); |
| 155 | 156 | ||
| 156 | serializer = [this, enumeration]() { | 157 | serializer = [this, enumeration]() { |
| @@ -159,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer, | |||
| 159 | }; | 160 | }; |
| 160 | 161 | ||
| 161 | restore_func = [this, find_index]() { | 162 | restore_func = [this, find_index]() { |
| 162 | const u32 global_value = std::stoi(RelevantDefault(setting)); | 163 | const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0); |
| 163 | combobox->setCurrentIndex(find_index(global_value)); | 164 | combobox->setCurrentIndex(find_index(global_value)); |
| 164 | }; | 165 | }; |
| 165 | 166 | ||
| @@ -171,6 +172,65 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer, | |||
| 171 | return combobox; | 172 | return combobox; |
| 172 | } | 173 | } |
| 173 | 174 | ||
| 175 | QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer, | ||
| 176 | std::function<void()>& restore_func, | ||
| 177 | const std::function<void()>& touch) { | ||
| 178 | const auto type = setting.EnumIndex(); | ||
| 179 | |||
| 180 | QWidget* group = new QWidget(this); | ||
| 181 | QHBoxLayout* layout = new QHBoxLayout(group); | ||
| 182 | layout->setContentsMargins(0, 0, 0, 0); | ||
| 183 | group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); | ||
| 184 | |||
| 185 | const ComboboxTranslations* enumeration{nullptr}; | ||
| 186 | if (combobox_enumerations.contains(type)) { | ||
| 187 | enumeration = &combobox_enumerations.at(type); | ||
| 188 | for (const auto& [id, name] : *enumeration) { | ||
| 189 | QRadioButton* radio_button = new QRadioButton(name, group); | ||
| 190 | layout->addWidget(radio_button); | ||
| 191 | radio_buttons.push_back({id, radio_button}); | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | return group; | ||
| 195 | } | ||
| 196 | |||
| 197 | const auto get_selected = [=]() -> int { | ||
| 198 | for (const auto& [id, button] : radio_buttons) { | ||
| 199 | if (button->isChecked()) { | ||
| 200 | return id; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | return -1; | ||
| 204 | }; | ||
| 205 | |||
| 206 | const auto set_index = [=](u32 value) { | ||
| 207 | for (const auto& [id, button] : radio_buttons) { | ||
| 208 | button->setChecked(id == value); | ||
| 209 | } | ||
| 210 | }; | ||
| 211 | |||
| 212 | const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0); | ||
| 213 | set_index(setting_value); | ||
| 214 | |||
| 215 | serializer = [get_selected]() { | ||
| 216 | int current = get_selected(); | ||
| 217 | return std::to_string(current); | ||
| 218 | }; | ||
| 219 | |||
| 220 | restore_func = [this, set_index]() { | ||
| 221 | const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0); | ||
| 222 | set_index(global_value); | ||
| 223 | }; | ||
| 224 | |||
| 225 | if (!Settings::IsConfiguringGlobal()) { | ||
| 226 | for (const auto& [id, button] : radio_buttons) { | ||
| 227 | QObject::connect(button, &QAbstractButton::clicked, [touch]() { touch(); }); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | return group; | ||
| 232 | } | ||
| 233 | |||
| 174 | QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer, | 234 | QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer, |
| 175 | std::function<void()>& restore_func, | 235 | std::function<void()>& restore_func, |
| 176 | const std::function<void()>& touch, bool managed) { | 236 | const std::function<void()>& touch, bool managed) { |
| @@ -195,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer, | |||
| 195 | return line_edit; | 255 | return line_edit; |
| 196 | } | 256 | } |
| 197 | 257 | ||
| 258 | static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier, | ||
| 259 | QLabel* feedback, const QString& use_format, QSlider* slider, | ||
| 260 | std::function<std::string()>& serializer, | ||
| 261 | std::function<void()>& restore_func) { | ||
| 262 | const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0); | ||
| 263 | |||
| 264 | const auto update_feedback = [=](int value) { | ||
| 265 | int present = (reversed ? max_val - value : value) * multiplier + 0.5f; | ||
| 266 | feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); | ||
| 267 | }; | ||
| 268 | |||
| 269 | QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback); | ||
| 270 | update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0)); | ||
| 271 | |||
| 272 | slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0)); | ||
| 273 | slider->setMaximum(max_val); | ||
| 274 | slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0)); | ||
| 275 | |||
| 276 | serializer = [slider]() { return std::to_string(slider->value()); }; | ||
| 277 | restore_func = [slider, &setting]() { | ||
| 278 | slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)); | ||
| 279 | }; | ||
| 280 | } | ||
| 281 | |||
| 282 | static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier, | ||
| 283 | QLabel* feedback, const QString& use_format, QSlider* slider, | ||
| 284 | std::function<std::string()>& serializer, | ||
| 285 | std::function<void()>& restore_func) { | ||
| 286 | const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr); | ||
| 287 | const float min_val = std::strtof(setting.MinVal().c_str(), nullptr); | ||
| 288 | const float use_multiplier = | ||
| 289 | multiplier == default_multiplier ? default_float_multiplier : multiplier; | ||
| 290 | |||
| 291 | const auto update_feedback = [=](float value) { | ||
| 292 | int present = (reversed ? max_val - value : value) + 0.5f; | ||
| 293 | feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); | ||
| 294 | }; | ||
| 295 | |||
| 296 | QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback); | ||
| 297 | update_feedback(std::strtof(setting.ToString().c_str(), nullptr)); | ||
| 298 | |||
| 299 | slider->setMinimum(min_val * use_multiplier); | ||
| 300 | slider->setMaximum(max_val * use_multiplier); | ||
| 301 | slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier); | ||
| 302 | |||
| 303 | serializer = [slider, use_multiplier]() { | ||
| 304 | return std::to_string(slider->value() / use_multiplier); | ||
| 305 | }; | ||
| 306 | restore_func = [slider, &setting, use_multiplier]() { | ||
| 307 | slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier); | ||
| 308 | }; | ||
| 309 | } | ||
| 310 | |||
| 198 | QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, | 311 | QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, |
| 199 | std::function<std::string()>& serializer, | 312 | std::function<std::string()>& serializer, |
| 200 | std::function<void()>& restore_func, | 313 | std::function<void()>& restore_func, |
| @@ -218,27 +331,20 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi | |||
| 218 | 331 | ||
| 219 | layout->setContentsMargins(0, 0, 0, 0); | 332 | layout->setContentsMargins(0, 0, 0, 0); |
| 220 | 333 | ||
| 221 | int max_val = std::stoi(setting.MaxVal()); | 334 | QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix; |
| 222 | |||
| 223 | QString suffix = | ||
| 224 | given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix; | ||
| 225 | 335 | ||
| 226 | const QString use_format = QStringLiteral("%1").append(suffix); | 336 | const QString use_format = QStringLiteral("%1").append(suffix); |
| 227 | 337 | ||
| 228 | QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { | 338 | if (setting.IsIntegral()) { |
| 229 | int present = (reversed ? max_val - value : value) * multiplier + 0.5f; | 339 | CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer, |
| 230 | feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); | 340 | restore_func); |
| 231 | }); | 341 | } else { |
| 232 | 342 | CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer, | |
| 233 | slider->setMinimum(std::stoi(setting.MinVal())); | 343 | restore_func); |
| 234 | slider->setMaximum(max_val); | 344 | } |
| 235 | slider->setValue(std::stoi(setting.ToString())); | ||
| 236 | 345 | ||
| 237 | slider->setInvertedAppearance(reversed); | 346 | slider->setInvertedAppearance(reversed); |
| 238 | 347 | ||
| 239 | serializer = [this]() { return std::to_string(slider->value()); }; | ||
| 240 | restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); }; | ||
| 241 | |||
| 242 | if (!Settings::IsConfiguringGlobal()) { | 348 | if (!Settings::IsConfiguringGlobal()) { |
| 243 | QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); | 349 | QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); |
| 244 | } | 350 | } |
| @@ -250,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix, | |||
| 250 | std::function<std::string()>& serializer, | 356 | std::function<std::string()>& serializer, |
| 251 | std::function<void()>& restore_func, | 357 | std::function<void()>& restore_func, |
| 252 | const std::function<void()>& touch) { | 358 | const std::function<void()>& touch) { |
| 253 | const int min_val = | 359 | const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0); |
| 254 | setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); | 360 | const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0); |
| 255 | const int max_val = | 361 | const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0); |
| 256 | setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max(); | ||
| 257 | const int default_val = std::stoi(setting.ToString()); | ||
| 258 | 362 | ||
| 259 | QString suffix = | 363 | QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix; |
| 260 | given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix; | ||
| 261 | 364 | ||
| 262 | spinbox = new QSpinBox(this); | 365 | spinbox = new QSpinBox(this); |
| 263 | spinbox->setRange(min_val, max_val); | 366 | spinbox->setRange(min_val, max_val); |
| @@ -268,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix, | |||
| 268 | serializer = [this]() { return std::to_string(spinbox->value()); }; | 371 | serializer = [this]() { return std::to_string(spinbox->value()); }; |
| 269 | 372 | ||
| 270 | restore_func = [this]() { | 373 | restore_func = [this]() { |
| 271 | auto value{std::stol(RelevantDefault(setting))}; | 374 | auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)}; |
| 272 | spinbox->setValue(value); | 375 | spinbox->setValue(value); |
| 273 | }; | 376 | }; |
| 274 | 377 | ||
| 275 | if (!Settings::IsConfiguringGlobal()) { | 378 | if (!Settings::IsConfiguringGlobal()) { |
| 276 | QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { | 379 | QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { |
| 277 | if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { | 380 | if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) { |
| 278 | touch(); | 381 | touch(); |
| 279 | } | 382 | } |
| 280 | }); | 383 | }); |
| @@ -283,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix, | |||
| 283 | return spinbox; | 386 | return spinbox; |
| 284 | } | 387 | } |
| 285 | 388 | ||
| 389 | QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix, | ||
| 390 | std::function<std::string()>& serializer, | ||
| 391 | std::function<void()>& restore_func, | ||
| 392 | const std::function<void()>& touch) { | ||
| 393 | const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr); | ||
| 394 | const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr); | ||
| 395 | const auto default_val = std::strtod(setting.ToString().c_str(), nullptr); | ||
| 396 | |||
| 397 | QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix; | ||
| 398 | |||
| 399 | double_spinbox = new QDoubleSpinBox(this); | ||
| 400 | double_spinbox->setRange(min_val, max_val); | ||
| 401 | double_spinbox->setValue(default_val); | ||
| 402 | double_spinbox->setSuffix(suffix); | ||
| 403 | double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); | ||
| 404 | |||
| 405 | serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); }; | ||
| 406 | |||
| 407 | restore_func = [this]() { | ||
| 408 | auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)}; | ||
| 409 | double_spinbox->setValue(value); | ||
| 410 | }; | ||
| 411 | |||
| 412 | if (!Settings::IsConfiguringGlobal()) { | ||
| 413 | QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), | ||
| 414 | [this, touch]() { | ||
| 415 | if (double_spinbox->value() != | ||
| 416 | std::strtod(setting.ToStringGlobal().c_str(), nullptr)) { | ||
| 417 | touch(); | ||
| 418 | } | ||
| 419 | }); | ||
| 420 | } | ||
| 421 | |||
| 422 | return double_spinbox; | ||
| 423 | } | ||
| 424 | |||
| 286 | QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, | 425 | QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, |
| 287 | std::function<void()>& restore_func, | 426 | std::function<void()>& restore_func, |
| 288 | const std::function<void()>& touch) { | 427 | const std::function<void()>& touch) { |
| @@ -292,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, | |||
| 292 | } | 431 | } |
| 293 | 432 | ||
| 294 | auto to_hex = [=](const std::string& input) { | 433 | auto to_hex = [=](const std::string& input) { |
| 295 | return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); | 434 | return QString::fromStdString( |
| 435 | fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0))); | ||
| 296 | }; | 436 | }; |
| 297 | 437 | ||
| 298 | QRegularExpressionValidator* regex = new QRegularExpressionValidator( | 438 | QRegularExpressionValidator* regex = new QRegularExpressionValidator( |
| @@ -305,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, | |||
| 305 | line_edit->setValidator(regex); | 445 | line_edit->setValidator(regex); |
| 306 | 446 | ||
| 307 | auto hex_to_dec = [this]() -> std::string { | 447 | auto hex_to_dec = [this]() -> std::string { |
| 308 | return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); | 448 | return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16)); |
| 309 | }; | 449 | }; |
| 310 | 450 | ||
| 311 | serializer = [hex_to_dec]() { return hex_to_dec(); }; | 451 | serializer = [hex_to_dec]() { return hex_to_dec(); }; |
| @@ -325,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict, | |||
| 325 | std::function<void()>& restore_func, | 465 | std::function<void()>& restore_func, |
| 326 | const std::function<void()>& touch) { | 466 | const std::function<void()>& touch) { |
| 327 | const long long current_time = QDateTime::currentSecsSinceEpoch(); | 467 | const long long current_time = QDateTime::currentSecsSinceEpoch(); |
| 328 | const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); | 468 | const s64 the_time = |
| 469 | disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0); | ||
| 329 | const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); | 470 | const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); |
| 330 | 471 | ||
| 331 | date_time_edit = new QDateTimeEdit(this); | 472 | date_time_edit = new QDateTimeEdit(this); |
| @@ -338,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict, | |||
| 338 | auto get_clear_val = [this, restrict, current_time]() { | 479 | auto get_clear_val = [this, restrict, current_time]() { |
| 339 | return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { | 480 | return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { |
| 340 | if (restrict && checkbox->checkState() == Qt::Checked) { | 481 | if (restrict && checkbox->checkState() == Qt::Checked) { |
| 341 | return std::stoll(RelevantDefault(setting)); | 482 | return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0); |
| 342 | } | 483 | } |
| 343 | return current_time; | 484 | return current_time; |
| 344 | }()); | 485 | }()); |
| @@ -410,6 +551,8 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu | |||
| 410 | return RequestType::Slider; | 551 | return RequestType::Slider; |
| 411 | case Settings::Specialization::Countable: | 552 | case Settings::Specialization::Countable: |
| 412 | return RequestType::SpinBox; | 553 | return RequestType::SpinBox; |
| 554 | case Settings::Specialization::Radio: | ||
| 555 | return RequestType::RadioGroup; | ||
| 413 | default: | 556 | default: |
| 414 | break; | 557 | break; |
| 415 | } | 558 | } |
| @@ -438,9 +581,12 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu | |||
| 438 | if (setting.TypeId() == typeid(bool)) { | 581 | if (setting.TypeId() == typeid(bool)) { |
| 439 | data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch); | 582 | data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch); |
| 440 | } else if (setting.IsEnum()) { | 583 | } else if (setting.IsEnum()) { |
| 441 | data_component = CreateCombobox(serializer, restore_func, touch); | 584 | if (request == RequestType::RadioGroup) { |
| 442 | } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || | 585 | data_component = CreateRadioGroup(serializer, restore_func, touch); |
| 443 | type == typeid(s64) || type == typeid(u8)) { | 586 | } else { |
| 587 | data_component = CreateCombobox(serializer, restore_func, touch); | ||
| 588 | } | ||
| 589 | } else if (setting.IsIntegral()) { | ||
| 444 | switch (request) { | 590 | switch (request) { |
| 445 | case RequestType::Slider: | 591 | case RequestType::Slider: |
| 446 | case RequestType::ReverseSlider: | 592 | case RequestType::ReverseSlider: |
| @@ -467,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu | |||
| 467 | default: | 613 | default: |
| 468 | UNIMPLEMENTED(); | 614 | UNIMPLEMENTED(); |
| 469 | } | 615 | } |
| 616 | } else if (setting.IsFloatingPoint()) { | ||
| 617 | switch (request) { | ||
| 618 | case RequestType::Default: | ||
| 619 | case RequestType::SpinBox: | ||
| 620 | data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch); | ||
| 621 | break; | ||
| 622 | case RequestType::Slider: | ||
| 623 | case RequestType::ReverseSlider: | ||
| 624 | data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix, | ||
| 625 | serializer, restore_func, touch); | ||
| 626 | break; | ||
| 627 | default: | ||
| 628 | UNIMPLEMENTED(); | ||
| 629 | } | ||
| 470 | } else if (type == typeid(std::string)) { | 630 | } else if (type == typeid(std::string)) { |
| 471 | switch (request) { | 631 | switch (request) { |
| 472 | case RequestType::Default: | 632 | case RequestType::Default: |
| @@ -571,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati | |||
| 571 | return std::pair{translations.at(id).first, translations.at(id).second}; | 731 | return std::pair{translations.at(id).first, translations.at(id).second}; |
| 572 | } | 732 | } |
| 573 | LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); | 733 | LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); |
| 574 | return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; | 734 | return std::pair{QString::fromStdString(setting_label), QStringLiteral()}; |
| 575 | }(); | 735 | }(); |
| 576 | 736 | ||
| 577 | if (label == QStringLiteral("")) { | 737 | if (label == QStringLiteral()) { |
| 578 | LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", | 738 | LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", |
| 579 | setting.GetLabel()); | 739 | setting.GetLabel()); |
| 580 | return; | 740 | return; |
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h index e64693bab..226284cf3 100644 --- a/src/yuzu/configuration/shared_widget.h +++ b/src/yuzu/configuration/shared_widget.h | |||
| @@ -22,6 +22,8 @@ class QObject; | |||
| 22 | class QPushButton; | 22 | class QPushButton; |
| 23 | class QSlider; | 23 | class QSlider; |
| 24 | class QSpinBox; | 24 | class QSpinBox; |
| 25 | class QDoubleSpinBox; | ||
| 26 | class QRadioButton; | ||
| 25 | 27 | ||
| 26 | namespace Settings { | 28 | namespace Settings { |
| 27 | class BasicSetting; | 29 | class BasicSetting; |
| @@ -38,9 +40,14 @@ enum class RequestType { | |||
| 38 | LineEdit, | 40 | LineEdit, |
| 39 | HexEdit, | 41 | HexEdit, |
| 40 | DateTimeEdit, | 42 | DateTimeEdit, |
| 43 | RadioGroup, | ||
| 41 | MaxEnum, | 44 | MaxEnum, |
| 42 | }; | 45 | }; |
| 43 | 46 | ||
| 47 | constexpr float default_multiplier{1.f}; | ||
| 48 | constexpr float default_float_multiplier{100.f}; | ||
| 49 | static const QString default_suffix = QStringLiteral(); | ||
| 50 | |||
| 44 | class Widget : public QWidget { | 51 | class Widget : public QWidget { |
| 45 | Q_OBJECT | 52 | Q_OBJECT |
| 46 | 53 | ||
| @@ -64,8 +71,9 @@ public: | |||
| 64 | const ComboboxTranslationMap& combobox_translations, QWidget* parent, | 71 | const ComboboxTranslationMap& combobox_translations, QWidget* parent, |
| 65 | bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, | 72 | bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, |
| 66 | RequestType request = RequestType::Default, bool managed = true, | 73 | RequestType request = RequestType::Default, bool managed = true, |
| 67 | float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, | 74 | float multiplier = default_multiplier, |
| 68 | const QString& suffix = QStringLiteral("")); | 75 | Settings::BasicSetting* other_setting = nullptr, |
| 76 | const QString& suffix = default_suffix); | ||
| 69 | virtual ~Widget(); | 77 | virtual ~Widget(); |
| 70 | 78 | ||
| 71 | /** | 79 | /** |
| @@ -87,10 +95,12 @@ public: | |||
| 87 | QPushButton* restore_button{}; ///< Restore button for custom configurations | 95 | QPushButton* restore_button{}; ///< Restore button for custom configurations |
| 88 | QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit | 96 | QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit |
| 89 | QSpinBox* spinbox{}; | 97 | QSpinBox* spinbox{}; |
| 98 | QDoubleSpinBox* double_spinbox{}; | ||
| 90 | QCheckBox* checkbox{}; | 99 | QCheckBox* checkbox{}; |
| 91 | QSlider* slider{}; | 100 | QSlider* slider{}; |
| 92 | QComboBox* combobox{}; | 101 | QComboBox* combobox{}; |
| 93 | QDateTimeEdit* date_time_edit{}; | 102 | QDateTimeEdit* date_time_edit{}; |
| 103 | std::vector<std::pair<u32, QRadioButton*>> radio_buttons{}; | ||
| 94 | 104 | ||
| 95 | private: | 105 | private: |
| 96 | void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed, | 106 | void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed, |
| @@ -106,6 +116,9 @@ private: | |||
| 106 | QWidget* CreateCombobox(std::function<std::string()>& serializer, | 116 | QWidget* CreateCombobox(std::function<std::string()>& serializer, |
| 107 | std::function<void()>& restore_func, | 117 | std::function<void()>& restore_func, |
| 108 | const std::function<void()>& touch); | 118 | const std::function<void()>& touch); |
| 119 | QWidget* CreateRadioGroup(std::function<std::string()>& serializer, | ||
| 120 | std::function<void()>& restore_func, | ||
| 121 | const std::function<void()>& touch); | ||
| 109 | QWidget* CreateLineEdit(std::function<std::string()>& serializer, | 122 | QWidget* CreateLineEdit(std::function<std::string()>& serializer, |
| 110 | std::function<void()>& restore_func, const std::function<void()>& touch, | 123 | std::function<void()>& restore_func, const std::function<void()>& touch, |
| 111 | bool managed = true); | 124 | bool managed = true); |
| @@ -120,6 +133,9 @@ private: | |||
| 120 | const std::function<void()>& touch); | 133 | const std::function<void()>& touch); |
| 121 | QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, | 134 | QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, |
| 122 | std::function<void()>& restore_func, const std::function<void()>& touch); | 135 | std::function<void()>& restore_func, const std::function<void()>& touch); |
| 136 | QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer, | ||
| 137 | std::function<void()>& restore_func, | ||
| 138 | const std::function<void()>& touch); | ||
| 123 | 139 | ||
| 124 | QWidget* parent; | 140 | QWidget* parent; |
| 125 | const TranslationMap& translations; | 141 | const TranslationMap& translations; |
| @@ -139,14 +155,15 @@ public: | |||
| 139 | Widget* BuildWidget(Settings::BasicSetting* setting, | 155 | Widget* BuildWidget(Settings::BasicSetting* setting, |
| 140 | std::vector<std::function<void(bool)>>& apply_funcs, | 156 | std::vector<std::function<void(bool)>>& apply_funcs, |
| 141 | RequestType request = RequestType::Default, bool managed = true, | 157 | RequestType request = RequestType::Default, bool managed = true, |
| 142 | float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, | 158 | float multiplier = default_multiplier, |
| 143 | const QString& suffix = QStringLiteral("")) const; | 159 | Settings::BasicSetting* other_setting = nullptr, |
| 160 | const QString& suffix = default_suffix) const; | ||
| 144 | 161 | ||
| 145 | Widget* BuildWidget(Settings::BasicSetting* setting, | 162 | Widget* BuildWidget(Settings::BasicSetting* setting, |
| 146 | std::vector<std::function<void(bool)>>& apply_funcs, | 163 | std::vector<std::function<void(bool)>>& apply_funcs, |
| 147 | Settings::BasicSetting* other_setting, | 164 | Settings::BasicSetting* other_setting, |
| 148 | RequestType request = RequestType::Default, | 165 | RequestType request = RequestType::Default, |
| 149 | const QString& suffix = QStringLiteral("")) const; | 166 | const QString& suffix = default_suffix) const; |
| 150 | 167 | ||
| 151 | const ComboboxTranslationMap& ComboboxTranslations() const; | 168 | const ComboboxTranslationMap& ComboboxTranslations() const; |
| 152 | 169 | ||
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 465084fea..f254c1e1c 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -214,13 +214,17 @@ void GameList::OnTextChanged(const QString& new_text) { | |||
| 214 | const int children_count = folder->rowCount(); | 214 | const int children_count = folder->rowCount(); |
| 215 | for (int j = 0; j < children_count; ++j) { | 215 | for (int j = 0; j < children_count; ++j) { |
| 216 | ++children_total; | 216 | ++children_total; |
| 217 | |||
| 217 | const QStandardItem* child = folder->child(j, 0); | 218 | const QStandardItem* child = folder->child(j, 0); |
| 219 | |||
| 220 | const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong(); | ||
| 221 | |||
| 218 | const QString file_path = | 222 | const QString file_path = |
| 219 | child->data(GameListItemPath::FullPathRole).toString().toLower(); | 223 | child->data(GameListItemPath::FullPathRole).toString().toLower(); |
| 220 | const QString file_title = | 224 | const QString file_title = |
| 221 | child->data(GameListItemPath::TitleRole).toString().toLower(); | 225 | child->data(GameListItemPath::TitleRole).toString().toLower(); |
| 222 | const QString file_program_id = | 226 | const QString file_program_id = |
| 223 | child->data(GameListItemPath::ProgramIdRole).toString().toLower(); | 227 | QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'}); |
| 224 | 228 | ||
| 225 | // Only items which filename in combination with its title contains all words | 229 | // Only items which filename in combination with its title contains all words |
| 226 | // that are in the searchfield will be visible in the gamelist | 230 | // that are in the searchfield will be visible in the gamelist |
| @@ -231,7 +235,7 @@ void GameList::OnTextChanged(const QString& new_text) { | |||
| 231 | file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + | 235 | file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + |
| 232 | file_title; | 236 | file_title; |
| 233 | if (ContainsAllWords(file_name, edit_filter_text) || | 237 | if (ContainsAllWords(file_name, edit_filter_text) || |
| 234 | (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { | 238 | (file_program_id.count() == 16 && file_program_id.contains(edit_filter_text))) { |
| 235 | tree_view->setRowHidden(j, folder_index, false); | 239 | tree_view->setRowHidden(j, folder_index, false); |
| 236 | ++result_count; | 240 | ++result_count; |
| 237 | } else { | 241 | } else { |
| @@ -553,6 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 553 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); | 557 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); |
| 554 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); | 558 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); |
| 555 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); | 559 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); |
| 560 | QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); | ||
| 556 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | 561 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); |
| 557 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 562 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 558 | #ifndef WIN32 | 563 | #ifndef WIN32 |
| @@ -584,10 +589,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 584 | emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); | 589 | emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); |
| 585 | }); | 590 | }); |
| 586 | connect(start_game, &QAction::triggered, [this, path]() { | 591 | connect(start_game, &QAction::triggered, [this, path]() { |
| 587 | emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal); | 592 | emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal, |
| 593 | AmLaunchType::UserInitiated); | ||
| 588 | }); | 594 | }); |
| 589 | connect(start_game_global, &QAction::triggered, [this, path]() { | 595 | connect(start_game_global, &QAction::triggered, [this, path]() { |
| 590 | emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global); | 596 | emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global, |
| 597 | AmLaunchType::UserInitiated); | ||
| 591 | }); | 598 | }); |
| 592 | connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { | 599 | connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { |
| 593 | emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); | 600 | emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); |
| @@ -624,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 624 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { | 631 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { |
| 625 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); | 632 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); |
| 626 | }); | 633 | }); |
| 634 | connect(verify_integrity, &QAction::triggered, | ||
| 635 | [this, path]() { emit VerifyIntegrityRequested(path); }); | ||
| 627 | connect(copy_tid, &QAction::triggered, | 636 | connect(copy_tid, &QAction::triggered, |
| 628 | [this, program_id]() { emit CopyTIDRequested(program_id); }); | 637 | [this, program_id]() { emit CopyTIDRequested(program_id); }); |
| 629 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { | 638 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 6c2f75e53..1fcbbf0ba 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -28,6 +28,7 @@ class GameListWorker; | |||
| 28 | class GameListSearchField; | 28 | class GameListSearchField; |
| 29 | class GameListDir; | 29 | class GameListDir; |
| 30 | class GMainWindow; | 30 | class GMainWindow; |
| 31 | enum class AmLaunchType; | ||
| 31 | enum class StartGameType; | 32 | enum class StartGameType; |
| 32 | 33 | ||
| 33 | namespace FileSys { | 34 | namespace FileSys { |
| @@ -103,7 +104,7 @@ public: | |||
| 103 | 104 | ||
| 104 | signals: | 105 | signals: |
| 105 | void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, | 106 | void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, |
| 106 | StartGameType type); | 107 | StartGameType type, AmLaunchType launch_type); |
| 107 | void GameChosen(const QString& game_path, const u64 title_id = 0); | 108 | void GameChosen(const QString& game_path, const u64 title_id = 0); |
| 108 | void ShouldCancelWorker(); | 109 | void ShouldCancelWorker(); |
| 109 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target, | 110 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target, |
| @@ -113,6 +114,7 @@ signals: | |||
| 113 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 114 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 114 | const std::string& game_path); | 115 | const std::string& game_path); |
| 115 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 116 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 117 | void VerifyIntegrityRequested(const std::string& game_path); | ||
| 116 | void CopyTIDRequested(u64 program_id); | 118 | void CopyTIDRequested(u64 program_id); |
| 117 | void CreateShortcut(u64 program_id, const std::string& game_path, | 119 | void CreateShortcut(u64 program_id, const std::string& game_path, |
| 118 | GameListShortcutTarget target); | 120 | GameListShortcutTarget target); |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 9404365b4..e7fb8a282 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -191,8 +191,9 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, | |||
| 191 | } | 191 | } |
| 192 | 192 | ||
| 193 | QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, | 193 | QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, |
| 194 | const std::vector<u8>& icon, Loader::AppLoader& loader, | 194 | const std::size_t size, const std::vector<u8>& icon, |
| 195 | u64 program_id, const CompatibilityList& compatibility_list, | 195 | Loader::AppLoader& loader, u64 program_id, |
| 196 | const CompatibilityList& compatibility_list, | ||
| 196 | const FileSys::PatchManager& patch) { | 197 | const FileSys::PatchManager& patch) { |
| 197 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 198 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| 198 | 199 | ||
| @@ -210,7 +211,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 210 | file_type_string, program_id), | 211 | file_type_string, program_id), |
| 211 | new GameListItemCompat(compatibility), | 212 | new GameListItemCompat(compatibility), |
| 212 | new GameListItem(file_type_string), | 213 | new GameListItem(file_type_string), |
| 213 | new GameListItemSize(Common::FS::GetSize(path)), | 214 | new GameListItemSize(size), |
| 214 | }; | 215 | }; |
| 215 | 216 | ||
| 216 | const auto patch_versions = GetGameListCachedObject( | 217 | const auto patch_versions = GetGameListCachedObject( |
| @@ -278,8 +279,8 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 278 | GetMetadataFromControlNCA(patch, *control, icon, name); | 279 | GetMetadataFromControlNCA(patch, *control, icon, name); |
| 279 | } | 280 | } |
| 280 | 281 | ||
| 281 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, | 282 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, |
| 282 | compatibility_list, patch), | 283 | program_id, compatibility_list, patch), |
| 283 | parent_dir); | 284 | parent_dir); |
| 284 | } | 285 | } |
| 285 | } | 286 | } |
| @@ -354,8 +355,9 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 354 | const FileSys::PatchManager patch{id, system.GetFileSystemController(), | 355 | const FileSys::PatchManager patch{id, system.GetFileSystemController(), |
| 355 | system.GetContentProvider()}; | 356 | system.GetContentProvider()}; |
| 356 | 357 | ||
| 357 | emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, id, | 358 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 358 | compatibility_list, patch), | 359 | Common::FS::GetSize(physical_name), icon, |
| 360 | *loader, id, compatibility_list, patch), | ||
| 359 | parent_dir); | 361 | parent_dir); |
| 360 | } | 362 | } |
| 361 | } else { | 363 | } else { |
| @@ -368,9 +370,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 368 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), | 370 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), |
| 369 | system.GetContentProvider()}; | 371 | system.GetContentProvider()}; |
| 370 | 372 | ||
| 371 | emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, | 373 | emit EntryReady( |
| 372 | program_id, compatibility_list, patch), | 374 | MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), |
| 373 | parent_dir); | 375 | icon, *loader, program_id, compatibility_list, patch), |
| 376 | parent_dir); | ||
| 374 | } | 377 | } |
| 375 | } | 378 | } |
| 376 | } else if (is_dir) { | 379 | } else if (is_dir) { |
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index 848239c35..56eee8d82 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h | |||
| @@ -4,10 +4,12 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <map> | 6 | #include <map> |
| 7 | #include <QKeySequence> | ||
| 8 | #include <QString> | ||
| 9 | #include <QWidget> | ||
| 7 | #include "core/hid/hid_types.h" | 10 | #include "core/hid/hid_types.h" |
| 8 | 11 | ||
| 9 | class QDialog; | 12 | class QDialog; |
| 10 | class QKeySequence; | ||
| 11 | class QSettings; | 13 | class QSettings; |
| 12 | class QShortcut; | 14 | class QShortcut; |
| 13 | class ControllerShortcut; | 15 | class ControllerShortcut; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 97ae9e49a..d32aa9615 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -8,6 +8,8 @@ | |||
| 8 | #include <iostream> | 8 | #include <iostream> |
| 9 | #include <memory> | 9 | #include <memory> |
| 10 | #include <thread> | 10 | #include <thread> |
| 11 | #include "core/loader/nca.h" | ||
| 12 | #include "core/tools/renderdoc.h" | ||
| 11 | #ifdef __APPLE__ | 13 | #ifdef __APPLE__ |
| 12 | #include <unistd.h> // for chdir | 14 | #include <unistd.h> // for chdir |
| 13 | #endif | 15 | #endif |
| @@ -442,8 +444,13 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 442 | "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" | 444 | "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" |
| 443 | "here for instructions to fix the issue</a>.")); | 445 | "here for instructions to fix the issue</a>.")); |
| 444 | 446 | ||
| 447 | #ifdef HAS_OPENGL | ||
| 445 | Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; | 448 | Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; |
| 449 | #else | ||
| 450 | Settings::values.renderer_backend = Settings::RendererBackend::Null; | ||
| 451 | #endif | ||
| 446 | 452 | ||
| 453 | UpdateAPIText(); | ||
| 447 | renderer_status_button->setDisabled(true); | 454 | renderer_status_button->setDisabled(true); |
| 448 | renderer_status_button->setChecked(false); | 455 | renderer_status_button->setChecked(false); |
| 449 | } else { | 456 | } else { |
| @@ -1158,9 +1165,9 @@ void GMainWindow::InitializeWidgets() { | |||
| 1158 | [this](const QPoint& menu_location) { | 1165 | [this](const QPoint& menu_location) { |
| 1159 | QMenu context_menu; | 1166 | QMenu context_menu; |
| 1160 | 1167 | ||
| 1161 | for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) { | 1168 | for (auto const& pair : Config::use_docked_mode_texts_map) { |
| 1162 | context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] { | 1169 | context_menu.addAction(pair.second, [this, &pair] { |
| 1163 | if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) { | 1170 | if (pair.first != Settings::values.use_docked_mode.GetValue()) { |
| 1164 | OnToggleDockedMode(); | 1171 | OnToggleDockedMode(); |
| 1165 | } | 1172 | } |
| 1166 | }); | 1173 | }); |
| @@ -1342,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() { | |||
| 1342 | connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { | 1349 | connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { |
| 1343 | Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); | 1350 | Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); |
| 1344 | }); | 1351 | }); |
| 1352 | connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] { | ||
| 1353 | if (Settings::values.enable_renderdoc_hotkey) { | ||
| 1354 | system->GetRenderdocAPI().ToggleCapture(); | ||
| 1355 | } | ||
| 1356 | }); | ||
| 1345 | connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { | 1357 | connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { |
| 1346 | if (Settings::values.mouse_enabled) { | 1358 | if (Settings::values.mouse_enabled) { |
| 1347 | Settings::values.mouse_panning = false; | 1359 | Settings::values.mouse_panning = false; |
| @@ -1447,6 +1459,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 1447 | &GMainWindow::OnGameListRemoveInstalledEntry); | 1459 | &GMainWindow::OnGameListRemoveInstalledEntry); |
| 1448 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | 1460 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); |
| 1449 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 1461 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 1462 | connect(game_list, &GameList::VerifyIntegrityRequested, this, | ||
| 1463 | &GMainWindow::OnGameListVerifyIntegrity); | ||
| 1450 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 1464 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 1451 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 1465 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 1452 | &GMainWindow::OnGameListNavigateToGamedbEntry); | 1466 | &GMainWindow::OnGameListNavigateToGamedbEntry); |
| @@ -1547,6 +1561,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 1547 | 1561 | ||
| 1548 | // Help | 1562 | // Help |
| 1549 | connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); | 1563 | connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); |
| 1564 | connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); | ||
| 1550 | connect_menu(ui->action_About, &GMainWindow::OnAbout); | 1565 | connect_menu(ui->action_About, &GMainWindow::OnAbout); |
| 1551 | } | 1566 | } |
| 1552 | 1567 | ||
| @@ -1698,7 +1713,8 @@ void GMainWindow::AllowOSSleep() { | |||
| 1698 | #endif | 1713 | #endif |
| 1699 | } | 1714 | } |
| 1700 | 1715 | ||
| 1701 | bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) { | 1716 | bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index, |
| 1717 | AmLaunchType launch_type) { | ||
| 1702 | // Shutdown previous session if the emu thread is still active... | 1718 | // Shutdown previous session if the emu thread is still active... |
| 1703 | if (emu_thread != nullptr) { | 1719 | if (emu_thread != nullptr) { |
| 1704 | ShutdownGame(); | 1720 | ShutdownGame(); |
| @@ -1710,6 +1726,10 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p | |||
| 1710 | 1726 | ||
| 1711 | system->SetFilesystem(vfs); | 1727 | system->SetFilesystem(vfs); |
| 1712 | 1728 | ||
| 1729 | if (launch_type == AmLaunchType::UserInitiated) { | ||
| 1730 | system->GetUserChannel().clear(); | ||
| 1731 | } | ||
| 1732 | |||
| 1713 | system->SetAppletFrontendSet({ | 1733 | system->SetAppletFrontendSet({ |
| 1714 | std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings | 1734 | std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings |
| 1715 | (UISettings::values.controller_applet_disabled.GetValue() == true) | 1735 | (UISettings::values.controller_applet_disabled.GetValue() == true) |
| @@ -1811,8 +1831,45 @@ bool GMainWindow::SelectAndSetCurrentUser( | |||
| 1811 | return true; | 1831 | return true; |
| 1812 | } | 1832 | } |
| 1813 | 1833 | ||
| 1834 | void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { | ||
| 1835 | // Ensure all NCAs are registered before launching the game | ||
| 1836 | const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read); | ||
| 1837 | if (!file) { | ||
| 1838 | return; | ||
| 1839 | } | ||
| 1840 | |||
| 1841 | auto loader = Loader::GetLoader(*system, file); | ||
| 1842 | if (!loader) { | ||
| 1843 | return; | ||
| 1844 | } | ||
| 1845 | |||
| 1846 | const auto file_type = loader->GetFileType(); | ||
| 1847 | if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { | ||
| 1848 | return; | ||
| 1849 | } | ||
| 1850 | |||
| 1851 | u64 program_id = 0; | ||
| 1852 | const auto res2 = loader->ReadProgramId(program_id); | ||
| 1853 | if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { | ||
| 1854 | provider->AddEntry(FileSys::TitleType::Application, | ||
| 1855 | FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, | ||
| 1856 | file); | ||
| 1857 | } else if (res2 == Loader::ResultStatus::Success && | ||
| 1858 | (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { | ||
| 1859 | const auto nsp = file_type == Loader::FileType::NSP | ||
| 1860 | ? std::make_shared<FileSys::NSP>(file) | ||
| 1861 | : FileSys::XCI{file}.GetSecurePartitionNSP(); | ||
| 1862 | for (const auto& title : nsp->GetNCAs()) { | ||
| 1863 | for (const auto& entry : title.second) { | ||
| 1864 | provider->AddEntry(entry.first.first, entry.first.second, title.first, | ||
| 1865 | entry.second->GetBaseFile()); | ||
| 1866 | } | ||
| 1867 | } | ||
| 1868 | } | ||
| 1869 | } | ||
| 1870 | |||
| 1814 | void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, | 1871 | void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, |
| 1815 | StartGameType type) { | 1872 | StartGameType type, AmLaunchType launch_type) { |
| 1816 | LOG_INFO(Frontend, "yuzu starting..."); | 1873 | LOG_INFO(Frontend, "yuzu starting..."); |
| 1817 | StoreRecentFile(filename); // Put the filename on top of the list | 1874 | StoreRecentFile(filename); // Put the filename on top of the list |
| 1818 | 1875 | ||
| @@ -1825,6 +1882,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t | |||
| 1825 | 1882 | ||
| 1826 | last_filename_booted = filename; | 1883 | last_filename_booted = filename; |
| 1827 | 1884 | ||
| 1885 | ConfigureFilesystemProvider(filename.toStdString()); | ||
| 1828 | const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); | 1886 | const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); |
| 1829 | const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index); | 1887 | const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index); |
| 1830 | 1888 | ||
| @@ -1855,7 +1913,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t | |||
| 1855 | } | 1913 | } |
| 1856 | } | 1914 | } |
| 1857 | 1915 | ||
| 1858 | if (!LoadROM(filename, program_id, program_index)) { | 1916 | if (!LoadROM(filename, program_id, program_index, launch_type)) { |
| 1859 | return; | 1917 | return; |
| 1860 | } | 1918 | } |
| 1861 | 1919 | ||
| @@ -1972,8 +2030,16 @@ bool GMainWindow::OnShutdownBegin() { | |||
| 1972 | 2030 | ||
| 1973 | emit EmulationStopping(); | 2031 | emit EmulationStopping(); |
| 1974 | 2032 | ||
| 2033 | int shutdown_time = 1000; | ||
| 2034 | |||
| 2035 | if (system->DebuggerEnabled()) { | ||
| 2036 | shutdown_time = 0; | ||
| 2037 | } else if (system->GetExitLocked()) { | ||
| 2038 | shutdown_time = 5000; | ||
| 2039 | } | ||
| 2040 | |||
| 1975 | shutdown_timer.setSingleShot(true); | 2041 | shutdown_timer.setSingleShot(true); |
| 1976 | shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); | 2042 | shutdown_timer.start(shutdown_time); |
| 1977 | connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); | 2043 | connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); |
| 1978 | connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); | 2044 | connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); |
| 1979 | 2045 | ||
| @@ -2229,40 +2295,62 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { | |||
| 2229 | QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); | 2295 | QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); |
| 2230 | } | 2296 | } |
| 2231 | 2297 | ||
| 2232 | static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) { | 2298 | static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog, |
| 2233 | std::size_t out = 0; | 2299 | const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, |
| 2234 | 2300 | bool full) { | |
| 2235 | for (const auto& subdir : dir->GetSubdirectories()) { | ||
| 2236 | out += 1 + CalculateRomFSEntrySize(subdir, full); | ||
| 2237 | } | ||
| 2238 | |||
| 2239 | return out + (full ? dir->GetFiles().size() : 0); | ||
| 2240 | } | ||
| 2241 | |||
| 2242 | static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src, | ||
| 2243 | const FileSys::VirtualDir& dest, std::size_t block_size, bool full) { | ||
| 2244 | if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) | 2301 | if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) |
| 2245 | return false; | 2302 | return false; |
| 2246 | if (dialog.wasCanceled()) | 2303 | if (dialog.wasCanceled()) |
| 2247 | return false; | 2304 | return false; |
| 2248 | 2305 | ||
| 2306 | std::vector<u8> buffer(CopyBufferSize); | ||
| 2307 | auto last_timestamp = std::chrono::steady_clock::now(); | ||
| 2308 | |||
| 2309 | const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file, | ||
| 2310 | const FileSys::VirtualFile& dest_file) { | ||
| 2311 | if (src_file == nullptr || dest_file == nullptr) { | ||
| 2312 | return false; | ||
| 2313 | } | ||
| 2314 | if (!dest_file->Resize(src_file->GetSize())) { | ||
| 2315 | return false; | ||
| 2316 | } | ||
| 2317 | |||
| 2318 | for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) { | ||
| 2319 | if (dialog.wasCanceled()) { | ||
| 2320 | dest_file->Resize(0); | ||
| 2321 | return false; | ||
| 2322 | } | ||
| 2323 | |||
| 2324 | using namespace std::literals::chrono_literals; | ||
| 2325 | const auto new_timestamp = std::chrono::steady_clock::now(); | ||
| 2326 | |||
| 2327 | if ((new_timestamp - last_timestamp) > 33ms) { | ||
| 2328 | last_timestamp = new_timestamp; | ||
| 2329 | dialog.setValue( | ||
| 2330 | static_cast<int>(std::min(read_size, total_size) * 100 / total_size)); | ||
| 2331 | QCoreApplication::processEvents(); | ||
| 2332 | } | ||
| 2333 | |||
| 2334 | const auto read = src_file->Read(buffer.data(), buffer.size(), i); | ||
| 2335 | dest_file->Write(buffer.data(), read, i); | ||
| 2336 | |||
| 2337 | read_size += read; | ||
| 2338 | } | ||
| 2339 | |||
| 2340 | return true; | ||
| 2341 | }; | ||
| 2342 | |||
| 2249 | if (full) { | 2343 | if (full) { |
| 2250 | for (const auto& file : src->GetFiles()) { | 2344 | for (const auto& file : src->GetFiles()) { |
| 2251 | const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); | 2345 | const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); |
| 2252 | if (!FileSys::VfsRawCopy(file, out, block_size)) | 2346 | if (!QtRawCopy(file, out)) |
| 2253 | return false; | ||
| 2254 | dialog.setValue(dialog.value() + 1); | ||
| 2255 | if (dialog.wasCanceled()) | ||
| 2256 | return false; | 2347 | return false; |
| 2257 | } | 2348 | } |
| 2258 | } | 2349 | } |
| 2259 | 2350 | ||
| 2260 | for (const auto& dir : src->GetSubdirectories()) { | 2351 | for (const auto& dir : src->GetSubdirectories()) { |
| 2261 | const auto out = dest->CreateSubdirectory(dir->GetName()); | 2352 | const auto out = dest->CreateSubdirectory(dir->GetName()); |
| 2262 | if (!RomFSRawCopy(dialog, dir, out, block_size, full)) | 2353 | if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full)) |
| 2263 | return false; | ||
| 2264 | dialog.setValue(dialog.value() + 1); | ||
| 2265 | if (dialog.wasCanceled()) | ||
| 2266 | return false; | 2354 | return false; |
| 2267 | } | 2355 | } |
| 2268 | 2356 | ||
| @@ -2535,16 +2623,34 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2535 | return; | 2623 | return; |
| 2536 | } | 2624 | } |
| 2537 | 2625 | ||
| 2538 | FileSys::VirtualFile file; | 2626 | FileSys::VirtualFile packed_update_raw{}; |
| 2539 | if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { | 2627 | loader->ReadUpdateRaw(packed_update_raw); |
| 2628 | |||
| 2629 | const auto& installed = system->GetContentProvider(); | ||
| 2630 | |||
| 2631 | u64 title_id{}; | ||
| 2632 | u8 raw_type{}; | ||
| 2633 | if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) { | ||
| 2634 | failed(); | ||
| 2635 | return; | ||
| 2636 | } | ||
| 2637 | |||
| 2638 | const auto type = static_cast<FileSys::ContentRecordType>(raw_type); | ||
| 2639 | const auto base_nca = installed.GetEntry(title_id, type); | ||
| 2640 | if (!base_nca) { | ||
| 2540 | failed(); | 2641 | failed(); |
| 2541 | return; | 2642 | return; |
| 2542 | } | 2643 | } |
| 2543 | 2644 | ||
| 2544 | const auto& installed = system->GetContentProvider(); | 2645 | const FileSys::NCA update_nca{packed_update_raw, nullptr}; |
| 2545 | const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); | 2646 | if (type != FileSys::ContentRecordType::Program || |
| 2647 | update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS || | ||
| 2648 | update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) { | ||
| 2649 | packed_update_raw = {}; | ||
| 2650 | } | ||
| 2546 | 2651 | ||
| 2547 | if (!romfs_title_id) { | 2652 | const auto base_romfs = base_nca->GetRomFS(); |
| 2653 | if (!base_romfs) { | ||
| 2548 | failed(); | 2654 | failed(); |
| 2549 | return; | 2655 | return; |
| 2550 | } | 2656 | } |
| @@ -2553,26 +2659,12 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2553 | target == DumpRomFSTarget::Normal | 2659 | target == DumpRomFSTarget::Normal |
| 2554 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) | 2660 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) |
| 2555 | : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; | 2661 | : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; |
| 2556 | const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); | 2662 | const auto romfs_dir = fmt::format("{:016X}/romfs", title_id); |
| 2557 | 2663 | ||
| 2558 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); | 2664 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); |
| 2559 | 2665 | ||
| 2560 | FileSys::VirtualFile romfs; | 2666 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed}; |
| 2561 | 2667 | auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false); | |
| 2562 | if (*romfs_title_id == program_id) { | ||
| 2563 | const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); | ||
| 2564 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; | ||
| 2565 | romfs = | ||
| 2566 | pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false); | ||
| 2567 | } else { | ||
| 2568 | romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); | ||
| 2569 | } | ||
| 2570 | |||
| 2571 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | ||
| 2572 | if (extracted == nullptr) { | ||
| 2573 | failed(); | ||
| 2574 | return; | ||
| 2575 | } | ||
| 2576 | 2668 | ||
| 2577 | const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); | 2669 | const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); |
| 2578 | 2670 | ||
| @@ -2596,11 +2688,16 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2596 | return; | 2688 | return; |
| 2597 | } | 2689 | } |
| 2598 | 2690 | ||
| 2691 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | ||
| 2692 | if (extracted == nullptr) { | ||
| 2693 | failed(); | ||
| 2694 | return; | ||
| 2695 | } | ||
| 2696 | |||
| 2599 | const auto full = res == selections.constFirst(); | 2697 | const auto full = res == selections.constFirst(); |
| 2600 | const auto entry_size = CalculateRomFSEntrySize(extracted, full); | ||
| 2601 | 2698 | ||
| 2602 | // The minimum required space is the size of the extracted RomFS + 1 GiB | 2699 | // The expected required space is the size of the RomFS + 1 GiB |
| 2603 | const auto minimum_free_space = extracted->GetSize() + 0x40000000; | 2700 | const auto minimum_free_space = romfs->GetSize() + 0x40000000; |
| 2604 | 2701 | ||
| 2605 | if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { | 2702 | if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { |
| 2606 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | 2703 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), |
| @@ -2611,12 +2708,15 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2611 | return; | 2708 | return; |
| 2612 | } | 2709 | } |
| 2613 | 2710 | ||
| 2614 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, | 2711 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 100, this); |
| 2615 | static_cast<s32>(entry_size), this); | ||
| 2616 | progress.setWindowModality(Qt::WindowModal); | 2712 | progress.setWindowModality(Qt::WindowModal); |
| 2617 | progress.setMinimumDuration(100); | 2713 | progress.setMinimumDuration(100); |
| 2714 | progress.setAutoClose(false); | ||
| 2715 | progress.setAutoReset(false); | ||
| 2716 | |||
| 2717 | size_t read_size = 0; | ||
| 2618 | 2718 | ||
| 2619 | if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) { | 2719 | if (RomFSRawCopy(romfs->GetSize(), read_size, progress, extracted, out, full)) { |
| 2620 | progress.close(); | 2720 | progress.close(); |
| 2621 | QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), | 2721 | QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), |
| 2622 | tr("The operation completed successfully.")); | 2722 | tr("The operation completed successfully.")); |
| @@ -2628,6 +2728,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2628 | } | 2728 | } |
| 2629 | } | 2729 | } |
| 2630 | 2730 | ||
| 2731 | void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | ||
| 2732 | const auto NotImplemented = [this] { | ||
| 2733 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), | ||
| 2734 | tr("File contents were not checked for validity.")); | ||
| 2735 | }; | ||
| 2736 | const auto Failed = [this] { | ||
| 2737 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2738 | tr("File contents may be corrupt.")); | ||
| 2739 | }; | ||
| 2740 | |||
| 2741 | const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 2742 | if (loader == nullptr) { | ||
| 2743 | NotImplemented(); | ||
| 2744 | return; | ||
| 2745 | } | ||
| 2746 | |||
| 2747 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||
| 2748 | progress.setWindowModality(Qt::WindowModal); | ||
| 2749 | progress.setMinimumDuration(100); | ||
| 2750 | progress.setAutoClose(false); | ||
| 2751 | progress.setAutoReset(false); | ||
| 2752 | |||
| 2753 | const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { | ||
| 2754 | if (progress.wasCanceled()) { | ||
| 2755 | return false; | ||
| 2756 | } | ||
| 2757 | |||
| 2758 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | ||
| 2759 | return true; | ||
| 2760 | }; | ||
| 2761 | |||
| 2762 | const auto status = loader->VerifyIntegrity(QtProgressCallback); | ||
| 2763 | if (progress.wasCanceled() || | ||
| 2764 | status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||
| 2765 | NotImplemented(); | ||
| 2766 | return; | ||
| 2767 | } | ||
| 2768 | |||
| 2769 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 2770 | Failed(); | ||
| 2771 | return; | ||
| 2772 | } | ||
| 2773 | |||
| 2774 | progress.close(); | ||
| 2775 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2776 | tr("The operation completed successfully.")); | ||
| 2777 | } | ||
| 2778 | |||
| 2631 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | 2779 | void GMainWindow::OnGameListCopyTID(u64 program_id) { |
| 2632 | QClipboard* clipboard = QGuiApplication::clipboard(); | 2780 | QClipboard* clipboard = QGuiApplication::clipboard(); |
| 2633 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | 2781 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); |
| @@ -3217,7 +3365,7 @@ void GMainWindow::OnPauseContinueGame() { | |||
| 3217 | } | 3365 | } |
| 3218 | 3366 | ||
| 3219 | void GMainWindow::OnStopGame() { | 3367 | void GMainWindow::OnStopGame() { |
| 3220 | if (system->GetExitLock() && !ConfirmForceLockedExit()) { | 3368 | if (system->GetExitLocked() && !ConfirmForceLockedExit()) { |
| 3221 | return; | 3369 | return; |
| 3222 | } | 3370 | } |
| 3223 | 3371 | ||
| @@ -3234,7 +3382,8 @@ void GMainWindow::OnLoadComplete() { | |||
| 3234 | 3382 | ||
| 3235 | void GMainWindow::OnExecuteProgram(std::size_t program_index) { | 3383 | void GMainWindow::OnExecuteProgram(std::size_t program_index) { |
| 3236 | ShutdownGame(); | 3384 | ShutdownGame(); |
| 3237 | BootGame(last_filename_booted, 0, program_index); | 3385 | BootGame(last_filename_booted, 0, program_index, StartGameType::Normal, |
| 3386 | AmLaunchType::ApplicationInitiated); | ||
| 3238 | } | 3387 | } |
| 3239 | 3388 | ||
| 3240 | void GMainWindow::OnExit() { | 3389 | void GMainWindow::OnExit() { |
| @@ -3630,7 +3779,7 @@ void GMainWindow::OnTasReset() { | |||
| 3630 | } | 3779 | } |
| 3631 | 3780 | ||
| 3632 | void GMainWindow::OnToggleDockedMode() { | 3781 | void GMainWindow::OnToggleDockedMode() { |
| 3633 | const bool is_docked = Settings::values.use_docked_mode.GetValue(); | 3782 | const bool is_docked = Settings::IsDockedMode(); |
| 3634 | auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | 3783 | auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); |
| 3635 | auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); | 3784 | auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); |
| 3636 | 3785 | ||
| @@ -3644,7 +3793,8 @@ void GMainWindow::OnToggleDockedMode() { | |||
| 3644 | controller_dialog->refreshConfiguration(); | 3793 | controller_dialog->refreshConfiguration(); |
| 3645 | } | 3794 | } |
| 3646 | 3795 | ||
| 3647 | Settings::values.use_docked_mode.SetValue(!is_docked); | 3796 | Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld |
| 3797 | : Settings::ConsoleMode::Docked); | ||
| 3648 | UpdateDockedButton(); | 3798 | UpdateDockedButton(); |
| 3649 | OnDockedModeChanged(is_docked, !is_docked, *system); | 3799 | OnDockedModeChanged(is_docked, !is_docked, *system); |
| 3650 | } | 3800 | } |
| @@ -3713,10 +3863,14 @@ void GMainWindow::OnToggleAdaptingFilter() { | |||
| 3713 | 3863 | ||
| 3714 | void GMainWindow::OnToggleGraphicsAPI() { | 3864 | void GMainWindow::OnToggleGraphicsAPI() { |
| 3715 | auto api = Settings::values.renderer_backend.GetValue(); | 3865 | auto api = Settings::values.renderer_backend.GetValue(); |
| 3716 | if (api == Settings::RendererBackend::OpenGL) { | 3866 | if (api != Settings::RendererBackend::Vulkan) { |
| 3717 | api = Settings::RendererBackend::Vulkan; | 3867 | api = Settings::RendererBackend::Vulkan; |
| 3718 | } else { | 3868 | } else { |
| 3869 | #ifdef HAS_OPENGL | ||
| 3719 | api = Settings::RendererBackend::OpenGL; | 3870 | api = Settings::RendererBackend::OpenGL; |
| 3871 | #else | ||
| 3872 | api = Settings::RendererBackend::Null; | ||
| 3873 | #endif | ||
| 3720 | } | 3874 | } |
| 3721 | Settings::values.renderer_backend.SetValue(api); | 3875 | Settings::values.renderer_backend.SetValue(api); |
| 3722 | renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); | 3876 | renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); |
| @@ -3860,6 +4014,108 @@ void GMainWindow::OnOpenYuzuFolder() { | |||
| 3860 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); | 4014 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); |
| 3861 | } | 4015 | } |
| 3862 | 4016 | ||
| 4017 | void GMainWindow::OnVerifyInstalledContents() { | ||
| 4018 | // Declare sizes. | ||
| 4019 | size_t total_size = 0; | ||
| 4020 | size_t processed_size = 0; | ||
| 4021 | |||
| 4022 | // Initialize a progress dialog. | ||
| 4023 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||
| 4024 | progress.setWindowModality(Qt::WindowModal); | ||
| 4025 | progress.setMinimumDuration(100); | ||
| 4026 | progress.setAutoClose(false); | ||
| 4027 | progress.setAutoReset(false); | ||
| 4028 | |||
| 4029 | // Declare a list of file names which failed to verify. | ||
| 4030 | std::vector<std::string> failed; | ||
| 4031 | |||
| 4032 | // Declare progress callback. | ||
| 4033 | auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) { | ||
| 4034 | if (progress.wasCanceled()) { | ||
| 4035 | return false; | ||
| 4036 | } | ||
| 4037 | progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size)); | ||
| 4038 | return true; | ||
| 4039 | }; | ||
| 4040 | |||
| 4041 | // Get content registries. | ||
| 4042 | auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | ||
| 4043 | auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||
| 4044 | |||
| 4045 | std::vector<FileSys::RegisteredCache*> content_providers; | ||
| 4046 | if (bis_contents) { | ||
| 4047 | content_providers.push_back(bis_contents); | ||
| 4048 | } | ||
| 4049 | if (user_contents) { | ||
| 4050 | content_providers.push_back(user_contents); | ||
| 4051 | } | ||
| 4052 | |||
| 4053 | // Get associated NCA files. | ||
| 4054 | std::vector<FileSys::VirtualFile> nca_files; | ||
| 4055 | |||
| 4056 | // Get all installed IDs. | ||
| 4057 | for (auto nca_provider : content_providers) { | ||
| 4058 | const auto entries = nca_provider->ListEntriesFilter(); | ||
| 4059 | |||
| 4060 | for (const auto& entry : entries) { | ||
| 4061 | auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||
| 4062 | if (!nca_file) { | ||
| 4063 | continue; | ||
| 4064 | } | ||
| 4065 | |||
| 4066 | total_size += nca_file->GetSize(); | ||
| 4067 | nca_files.push_back(std::move(nca_file)); | ||
| 4068 | } | ||
| 4069 | } | ||
| 4070 | |||
| 4071 | // Using the NCA loader, determine if all NCAs are valid. | ||
| 4072 | for (auto& nca_file : nca_files) { | ||
| 4073 | Loader::AppLoader_NCA nca_loader(nca_file); | ||
| 4074 | |||
| 4075 | auto status = nca_loader.VerifyIntegrity(QtProgressCallback); | ||
| 4076 | if (progress.wasCanceled()) { | ||
| 4077 | break; | ||
| 4078 | } | ||
| 4079 | if (status != Loader::ResultStatus::Success) { | ||
| 4080 | FileSys::NCA nca(nca_file); | ||
| 4081 | const auto title_id = nca.GetTitleId(); | ||
| 4082 | std::string title_name = "unknown"; | ||
| 4083 | |||
| 4084 | const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||
| 4085 | FileSys::ContentRecordType::Control); | ||
| 4086 | if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||
| 4087 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||
| 4088 | *provider}; | ||
| 4089 | const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||
| 4090 | if (nacp) { | ||
| 4091 | title_name = nacp->GetApplicationName(); | ||
| 4092 | } | ||
| 4093 | } | ||
| 4094 | |||
| 4095 | if (title_id > 0) { | ||
| 4096 | failed.push_back( | ||
| 4097 | fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||
| 4098 | } else { | ||
| 4099 | failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||
| 4100 | } | ||
| 4101 | } | ||
| 4102 | |||
| 4103 | processed_size += nca_file->GetSize(); | ||
| 4104 | } | ||
| 4105 | |||
| 4106 | progress.close(); | ||
| 4107 | |||
| 4108 | if (failed.size() > 0) { | ||
| 4109 | auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n"))); | ||
| 4110 | QMessageBox::critical( | ||
| 4111 | this, tr("Integrity verification failed!"), | ||
| 4112 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); | ||
| 4113 | } else { | ||
| 4114 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 4115 | tr("The operation completed successfully.")); | ||
| 4116 | } | ||
| 4117 | } | ||
| 4118 | |||
| 3863 | void GMainWindow::OnAbout() { | 4119 | void GMainWindow::OnAbout() { |
| 3864 | AboutDialog aboutDialog(this); | 4120 | AboutDialog aboutDialog(this); |
| 3865 | aboutDialog.exec(); | 4121 | aboutDialog.exec(); |
| @@ -4074,10 +4330,10 @@ void GMainWindow::UpdateGPUAccuracyButton() { | |||
| 4074 | } | 4330 | } |
| 4075 | 4331 | ||
| 4076 | void GMainWindow::UpdateDockedButton() { | 4332 | void GMainWindow::UpdateDockedButton() { |
| 4077 | const bool is_docked = Settings::values.use_docked_mode.GetValue(); | 4333 | const auto console_mode = Settings::values.use_docked_mode.GetValue(); |
| 4078 | dock_status_button->setChecked(is_docked); | 4334 | dock_status_button->setChecked(Settings::IsDockedMode()); |
| 4079 | dock_status_button->setText( | 4335 | dock_status_button->setText( |
| 4080 | Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper()); | 4336 | Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper()); |
| 4081 | } | 4337 | } |
| 4082 | 4338 | ||
| 4083 | void GMainWindow::UpdateAPIText() { | 4339 | void GMainWindow::UpdateAPIText() { |
| @@ -4305,28 +4561,41 @@ bool GMainWindow::CheckSystemArchiveDecryption() { | |||
| 4305 | return mii_nca->GetRomFS().get() != nullptr; | 4561 | return mii_nca->GetRomFS().get() != nullptr; |
| 4306 | } | 4562 | } |
| 4307 | 4563 | ||
| 4308 | std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, | 4564 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, |
| 4309 | u64 program_id) { | 4565 | u64* selected_title_id, u8* selected_content_record_type) { |
| 4310 | const auto dlc_entries = | 4566 | using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; |
| 4311 | installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | 4567 | boost::container::flat_map<u64, ContentInfo> available_title_ids; |
| 4312 | std::vector<FileSys::ContentProviderEntry> dlc_match; | 4568 | |
| 4313 | dlc_match.reserve(dlc_entries.size()); | 4569 | const auto RetrieveEntries = [&](FileSys::TitleType title_type, |
| 4314 | std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), | 4570 | FileSys::ContentRecordType record_type) { |
| 4315 | [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { | 4571 | const auto entries = installed.ListEntriesFilter(title_type, record_type); |
| 4316 | return FileSys::GetBaseTitleID(entry.title_id) == program_id && | 4572 | for (const auto& entry : entries) { |
| 4317 | installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; | 4573 | if (FileSys::GetBaseTitleID(entry.title_id) == program_id && |
| 4318 | }); | 4574 | installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) { |
| 4319 | 4575 | available_title_ids[entry.title_id] = {title_type, record_type}; | |
| 4320 | std::vector<u64> romfs_tids; | 4576 | } |
| 4321 | romfs_tids.push_back(program_id); | 4577 | } |
| 4322 | for (const auto& entry : dlc_match) { | 4578 | }; |
| 4323 | romfs_tids.push_back(entry.title_id); | 4579 | |
| 4324 | } | 4580 | RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program); |
| 4325 | 4581 | RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | |
| 4326 | if (romfs_tids.size() > 1) { | 4582 | |
| 4327 | QStringList list{QStringLiteral("Base")}; | 4583 | if (available_title_ids.empty()) { |
| 4328 | for (std::size_t i = 1; i < romfs_tids.size(); ++i) { | 4584 | return false; |
| 4329 | list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); | 4585 | } |
| 4586 | |||
| 4587 | size_t title_index = 0; | ||
| 4588 | |||
| 4589 | if (available_title_ids.size() > 1) { | ||
| 4590 | QStringList list; | ||
| 4591 | for (auto& [title_id, content_info] : available_title_ids) { | ||
| 4592 | const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id)); | ||
| 4593 | if (content_info.first == FileSys::TitleType::Application) { | ||
| 4594 | list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id)); | ||
| 4595 | } else { | ||
| 4596 | list.push_back( | ||
| 4597 | QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id)); | ||
| 4598 | } | ||
| 4330 | } | 4599 | } |
| 4331 | 4600 | ||
| 4332 | bool ok; | 4601 | bool ok; |
| @@ -4334,13 +4603,16 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv | |||
| 4334 | this, tr("Select RomFS Dump Target"), | 4603 | this, tr("Select RomFS Dump Target"), |
| 4335 | tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); | 4604 | tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); |
| 4336 | if (!ok) { | 4605 | if (!ok) { |
| 4337 | return {}; | 4606 | return false; |
| 4338 | } | 4607 | } |
| 4339 | 4608 | ||
| 4340 | return romfs_tids[list.indexOf(res)]; | 4609 | title_index = list.indexOf(res); |
| 4341 | } | 4610 | } |
| 4342 | 4611 | ||
| 4343 | return program_id; | 4612 | const auto selected_info = available_title_ids.nth(title_index); |
| 4613 | *selected_title_id = selected_info->first; | ||
| 4614 | *selected_content_record_type = static_cast<u8>(selected_info->second.second); | ||
| 4615 | return true; | ||
| 4344 | } | 4616 | } |
| 4345 | 4617 | ||
| 4346 | bool GMainWindow::ConfirmClose() { | 4618 | bool GMainWindow::ConfirmClose() { |
| @@ -4470,6 +4742,8 @@ void GMainWindow::RequestGameExit() { | |||
| 4470 | auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); | 4742 | auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); |
| 4471 | bool has_signalled = false; | 4743 | bool has_signalled = false; |
| 4472 | 4744 | ||
| 4745 | system->SetExitRequested(true); | ||
| 4746 | |||
| 4473 | if (applet_oe != nullptr) { | 4747 | if (applet_oe != nullptr) { |
| 4474 | applet_oe->GetMessageQueue()->RequestExit(); | 4748 | applet_oe->GetMessageQueue()->RequestExit(); |
| 4475 | has_signalled = true; | 4749 | has_signalled = true; |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 2cfb96257..cf191f698 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -58,6 +58,11 @@ enum class StartGameType { | |||
| 58 | Global, // Only uses global configuration | 58 | Global, // Only uses global configuration |
| 59 | }; | 59 | }; |
| 60 | 60 | ||
| 61 | enum class AmLaunchType { | ||
| 62 | UserInitiated, | ||
| 63 | ApplicationInitiated, | ||
| 64 | }; | ||
| 65 | |||
| 61 | namespace Core { | 66 | namespace Core { |
| 62 | enum class SystemResultStatus : u32; | 67 | enum class SystemResultStatus : u32; |
| 63 | class System; | 68 | class System; |
| @@ -239,9 +244,11 @@ private: | |||
| 239 | void PreventOSSleep(); | 244 | void PreventOSSleep(); |
| 240 | void AllowOSSleep(); | 245 | void AllowOSSleep(); |
| 241 | 246 | ||
| 242 | bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index); | 247 | bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index, |
| 248 | AmLaunchType launch_type); | ||
| 243 | void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0, | 249 | void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0, |
| 244 | StartGameType with_config = StartGameType::Normal); | 250 | StartGameType with_config = StartGameType::Normal, |
| 251 | AmLaunchType launch_type = AmLaunchType::UserInitiated); | ||
| 245 | void ShutdownGame(); | 252 | void ShutdownGame(); |
| 246 | 253 | ||
| 247 | void ShowTelemetryCallout(); | 254 | void ShowTelemetryCallout(); |
| @@ -313,6 +320,7 @@ private slots: | |||
| 313 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 320 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 314 | const std::string& game_path); | 321 | const std::string& game_path); |
| 315 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 322 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 323 | void OnGameListVerifyIntegrity(const std::string& game_path); | ||
| 316 | void OnGameListCopyTID(u64 program_id); | 324 | void OnGameListCopyTID(u64 program_id); |
| 317 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 325 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 318 | const CompatibilityList& compatibility_list); | 326 | const CompatibilityList& compatibility_list); |
| @@ -342,6 +350,7 @@ private slots: | |||
| 342 | void OnConfigurePerGame(); | 350 | void OnConfigurePerGame(); |
| 343 | void OnLoadAmiibo(); | 351 | void OnLoadAmiibo(); |
| 344 | void OnOpenYuzuFolder(); | 352 | void OnOpenYuzuFolder(); |
| 353 | void OnVerifyInstalledContents(); | ||
| 345 | void OnAbout(); | 354 | void OnAbout(); |
| 346 | void OnToggleFilterBar(); | 355 | void OnToggleFilterBar(); |
| 347 | void OnToggleStatusBar(); | 356 | void OnToggleStatusBar(); |
| @@ -375,7 +384,8 @@ private: | |||
| 375 | void RemoveAllTransferableShaderCaches(u64 program_id); | 384 | void RemoveAllTransferableShaderCaches(u64 program_id); |
| 376 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); | 385 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); |
| 377 | void RemoveCacheStorage(u64 program_id); | 386 | void RemoveCacheStorage(u64 program_id); |
| 378 | std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); | 387 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |
| 388 | u64* selected_title_id, u8* selected_content_record_type); | ||
| 379 | InstallResult InstallNSPXCI(const QString& filename); | 389 | InstallResult InstallNSPXCI(const QString& filename); |
| 380 | InstallResult InstallNCA(const QString& filename); | 390 | InstallResult InstallNCA(const QString& filename); |
| 381 | void MigrateConfigFiles(); | 391 | void MigrateConfigFiles(); |
| @@ -399,6 +409,7 @@ private: | |||
| 399 | void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); | 409 | void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); |
| 400 | bool CheckDarkMode(); | 410 | bool CheckDarkMode(); |
| 401 | bool CheckSystemArchiveDecryption(); | 411 | bool CheckSystemArchiveDecryption(); |
| 412 | void ConfigureFilesystemProvider(const std::string& filepath); | ||
| 402 | 413 | ||
| 403 | QString GetTasStateDescription() const; | 414 | QString GetTasStateDescription() const; |
| 404 | bool CreateShortcut(const std::string& shortcut_path, const std::string& title, | 415 | bool CreateShortcut(const std::string& shortcut_path, const std::string& title, |
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 013ba0ceb..e54d7d75d 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui | |||
| @@ -148,6 +148,7 @@ | |||
| 148 | <addaction name="action_Configure_Tas"/> | 148 | <addaction name="action_Configure_Tas"/> |
| 149 | </widget> | 149 | </widget> |
| 150 | <addaction name="action_Rederive"/> | 150 | <addaction name="action_Rederive"/> |
| 151 | <addaction name="action_Verify_installed_contents"/> | ||
| 151 | <addaction name="separator"/> | 152 | <addaction name="separator"/> |
| 152 | <addaction name="action_Capture_Screenshot"/> | 153 | <addaction name="action_Capture_Screenshot"/> |
| 153 | <addaction name="menuTAS"/> | 154 | <addaction name="menuTAS"/> |
| @@ -214,6 +215,11 @@ | |||
| 214 | <string>&Reinitialize keys...</string> | 215 | <string>&Reinitialize keys...</string> |
| 215 | </property> | 216 | </property> |
| 216 | </action> | 217 | </action> |
| 218 | <action name="action_Verify_installed_contents"> | ||
| 219 | <property name="text"> | ||
| 220 | <string>Verify installed contents</string> | ||
| 221 | </property> | ||
| 222 | </action> | ||
| 217 | <action name="action_About"> | 223 | <action name="action_About"> |
| 218 | <property name="text"> | 224 | <property name="text"> |
| 219 | <string>&About yuzu</string> | 225 | <string>&About yuzu</string> |
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index f03dc01dd..1c833767b 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp | |||
| @@ -36,4 +36,20 @@ bool IsDarkTheme() { | |||
| 36 | 36 | ||
| 37 | Values values = {}; | 37 | Values values = {}; |
| 38 | 38 | ||
| 39 | u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) { | ||
| 40 | switch (ratio) { | ||
| 41 | case Settings::AspectRatio::R4_3: | ||
| 42 | return height * 4 / 3; | ||
| 43 | case Settings::AspectRatio::R21_9: | ||
| 44 | return height * 21 / 9; | ||
| 45 | case Settings::AspectRatio::R16_10: | ||
| 46 | return height * 16 / 10; | ||
| 47 | case Settings::AspectRatio::R16_9: | ||
| 48 | case Settings::AspectRatio::Stretch: | ||
| 49 | // TODO: Move this function wherever appropriate to implement Stretched aspect | ||
| 50 | break; | ||
| 51 | } | ||
| 52 | return height * 16 / 9; | ||
| 53 | } | ||
| 54 | |||
| 39 | } // namespace UISettings | 55 | } // namespace UISettings |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index c9c89cee4..8efd63f31 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include <QVector> | 13 | #include <QVector> |
| 14 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 15 | #include "common/settings.h" | 15 | #include "common/settings.h" |
| 16 | #include "common/settings_enums.h" | ||
| 16 | 17 | ||
| 17 | using Settings::Category; | 18 | using Settings::Category; |
| 18 | using Settings::Setting; | 19 | using Settings::Setting; |
| @@ -127,8 +128,10 @@ struct Values { | |||
| 127 | // logging | 128 | // logging |
| 128 | Setting<bool> show_console{linkage, false, "showConsole", Category::Ui}; | 129 | Setting<bool> show_console{linkage, false, "showConsole", Category::Ui}; |
| 129 | 130 | ||
| 131 | // Screenshots | ||
| 130 | Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as", | 132 | Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as", |
| 131 | Category::Screenshots}; | 133 | Category::Screenshots}; |
| 134 | Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots}; | ||
| 132 | 135 | ||
| 133 | QString roms_path; | 136 | QString roms_path; |
| 134 | QString symbols_path; | 137 | QString symbols_path; |
| @@ -187,6 +190,8 @@ struct Values { | |||
| 187 | 190 | ||
| 188 | extern Values values; | 191 | extern Values values; |
| 189 | 192 | ||
| 193 | u32 CalculateWidth(u32 height, Settings::AspectRatio ratio); | ||
| 194 | |||
| 190 | } // namespace UISettings | 195 | } // namespace UISettings |
| 191 | 196 | ||
| 192 | Q_DECLARE_METATYPE(UISettings::GameDir*); | 197 | Q_DECLARE_METATYPE(UISettings::GameDir*); |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index c42d98709..0d25ff400 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -259,7 +259,7 @@ void Config::ReadValues() { | |||
| 259 | std::stringstream ss(title_list); | 259 | std::stringstream ss(title_list); |
| 260 | std::string line; | 260 | std::string line; |
| 261 | while (std::getline(ss, line, '|')) { | 261 | while (std::getline(ss, line, '|')) { |
| 262 | const auto title_id = std::stoul(line, nullptr, 16); | 262 | const auto title_id = std::strtoul(line.c_str(), nullptr, 16); |
| 263 | const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); | 263 | const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); |
| 264 | 264 | ||
| 265 | std::stringstream inner_ss(disabled_list); | 265 | std::stringstream inner_ss(disabled_list); |
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index d0433ffc6..087cfaa26 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp | |||
| @@ -264,8 +264,9 @@ int main(int argc, char** argv) { | |||
| 264 | nickname = match[1]; | 264 | nickname = match[1]; |
| 265 | password = match[2]; | 265 | password = match[2]; |
| 266 | address = match[3]; | 266 | address = match[3]; |
| 267 | if (!match[4].str().empty()) | 267 | if (!match[4].str().empty()) { |
| 268 | port = std::stoi(match[4]); | 268 | port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0)); |
| 269 | } | ||
| 269 | std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); | 270 | std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); |
| 270 | if (!std::regex_match(nickname, nickname_re)) { | 271 | if (!std::regex_match(nickname, nickname_re)) { |
| 271 | std::cout | 272 | std::cout |
| @@ -358,6 +359,7 @@ int main(int argc, char** argv) { | |||
| 358 | system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); | 359 | system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |
| 359 | system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); | 360 | system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); |
| 360 | system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); | 361 | system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); |
| 362 | system.GetUserChannel().clear(); | ||
| 361 | 363 | ||
| 362 | const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath)}; | 364 | const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath)}; |
| 363 | 365 | ||