diff options
Diffstat (limited to 'src')
15 files changed, 405 insertions, 131 deletions
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 1c9fb0675..c408485c6 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 | |||
| @@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.utils.Log | |||
| 23 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 23 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 24 | import org.yuzu.yuzu_emu.model.InstallResult | 24 | import org.yuzu.yuzu_emu.model.InstallResult |
| 25 | import org.yuzu.yuzu_emu.model.Patch | 25 | import org.yuzu.yuzu_emu.model.Patch |
| 26 | import org.yuzu.yuzu_emu.model.GameVerificationResult | ||
| 26 | 27 | ||
| 27 | /** | 28 | /** |
| 28 | * Class which contains methods that interact | 29 | * Class which contains methods that interact |
| @@ -565,6 +566,26 @@ object NativeLibrary { | |||
| 565 | external fun removeMod(programId: String, name: String) | 566 | external fun removeMod(programId: String, name: String) |
| 566 | 567 | ||
| 567 | /** | 568 | /** |
| 569 | * Verifies all installed content | ||
| 570 | * @param callback UI callback for verification progress. Return true in the callback to cancel. | ||
| 571 | * @return Array of content that failed verification. Successful if empty. | ||
| 572 | */ | ||
| 573 | external fun verifyInstalledContents( | ||
| 574 | callback: (max: Long, progress: Long) -> Boolean | ||
| 575 | ): Array<String> | ||
| 576 | |||
| 577 | /** | ||
| 578 | * Verifies the contents of a game | ||
| 579 | * @param path String path to a game | ||
| 580 | * @param callback UI callback for verification progress. Return true in the callback to cancel. | ||
| 581 | * @return Int that is meant to be converted to a [GameVerificationResult] | ||
| 582 | */ | ||
| 583 | external fun verifyGameContents( | ||
| 584 | path: String, | ||
| 585 | callback: (max: Long, progress: Long) -> Boolean | ||
| 586 | ): Int | ||
| 587 | |||
| 588 | /** | ||
| 568 | * Gets the save location for a specific game | 589 | * Gets the save location for a specific game |
| 569 | * | 590 | * |
| 570 | * @param programId String representation of a game's program ID | 591 | * @param programId String representation of a game's program ID |
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 16fb87614..71be2d0b2 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 | |||
| @@ -23,7 +23,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { | |||
| 23 | THEME("theme"), | 23 | THEME("theme"), |
| 24 | THEME_MODE("theme_mode"), | 24 | THEME_MODE("theme_mode"), |
| 25 | OVERLAY_SCALE("control_scale"), | 25 | OVERLAY_SCALE("control_scale"), |
| 26 | OVERLAY_OPACITY("control_opacity"); | 26 | OVERLAY_OPACITY("control_opacity"), |
| 27 | LOCK_DRAWER("lock_drawer"); | ||
| 27 | 28 | ||
| 28 | override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) | 29 | override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) |
| 29 | 30 | ||
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 47767454a..2a97ae14d 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 | |||
| @@ -182,11 +182,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | override fun onDrawerOpened(drawerView: View) { | 184 | override fun onDrawerOpened(drawerView: View) { |
| 185 | // No op | 185 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) |
| 186 | } | 186 | } |
| 187 | 187 | ||
| 188 | override fun onDrawerClosed(drawerView: View) { | 188 | override fun onDrawerClosed(drawerView: View) { |
| 189 | // No op | 189 | binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) |
| 190 | } | 190 | } |
| 191 | 191 | ||
| 192 | override fun onDrawerStateChanged(newState: Int) { | 192 | override fun onDrawerStateChanged(newState: Int) { |
| @@ -196,6 +196,28 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 196 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | 196 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) |
| 197 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = | 197 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = |
| 198 | game.title | 198 | game.title |
| 199 | |||
| 200 | binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { | ||
| 201 | val lockMode = IntSetting.LOCK_DRAWER.getInt() | ||
| 202 | val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { | ||
| 203 | R.string.unlock_drawer | ||
| 204 | } else { | ||
| 205 | R.string.lock_drawer | ||
| 206 | } | ||
| 207 | val iconId = if (lockMode == DrawerLayout.LOCK_MODE_UNLOCKED) { | ||
| 208 | R.drawable.ic_unlock | ||
| 209 | } else { | ||
| 210 | R.drawable.ic_lock | ||
| 211 | } | ||
| 212 | |||
| 213 | title = getString(titleId) | ||
| 214 | icon = ResourcesCompat.getDrawable( | ||
| 215 | resources, | ||
| 216 | iconId, | ||
| 217 | requireContext().theme | ||
| 218 | ) | ||
| 219 | } | ||
| 220 | |||
| 199 | binding.inGameMenu.setNavigationItemSelectedListener { | 221 | binding.inGameMenu.setNavigationItemSelectedListener { |
| 200 | when (it.itemId) { | 222 | when (it.itemId) { |
| 201 | R.id.menu_pause_emulation -> { | 223 | R.id.menu_pause_emulation -> { |
| @@ -242,6 +264,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 242 | true | 264 | true |
| 243 | } | 265 | } |
| 244 | 266 | ||
| 267 | R.id.menu_lock_drawer -> { | ||
| 268 | when (IntSetting.LOCK_DRAWER.getInt()) { | ||
| 269 | DrawerLayout.LOCK_MODE_UNLOCKED -> { | ||
| 270 | IntSetting.LOCK_DRAWER.setInt(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | ||
| 271 | it.title = resources.getString(R.string.unlock_drawer) | ||
| 272 | it.icon = ResourcesCompat.getDrawable( | ||
| 273 | resources, | ||
| 274 | R.drawable.ic_lock, | ||
| 275 | requireContext().theme | ||
| 276 | ) | ||
| 277 | } | ||
| 278 | |||
| 279 | DrawerLayout.LOCK_MODE_LOCKED_CLOSED -> { | ||
| 280 | IntSetting.LOCK_DRAWER.setInt(DrawerLayout.LOCK_MODE_UNLOCKED) | ||
| 281 | it.title = resources.getString(R.string.lock_drawer) | ||
| 282 | it.icon = ResourcesCompat.getDrawable( | ||
| 283 | resources, | ||
| 284 | R.drawable.ic_unlock, | ||
| 285 | requireContext().theme | ||
| 286 | ) | ||
| 287 | } | ||
| 288 | } | ||
| 289 | NativeConfig.saveGlobalConfig() | ||
| 290 | true | ||
| 291 | } | ||
| 292 | |||
| 245 | R.id.menu_exit -> { | 293 | R.id.menu_exit -> { |
| 246 | emulationState.stop() | 294 | emulationState.stop() |
| 247 | emulationViewModel.setIsEmulationStopping(true) | 295 | emulationViewModel.setIsEmulationStopping(true) |
| @@ -326,7 +374,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 326 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 374 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 327 | emulationViewModel.emulationStarted.collectLatest { | 375 | emulationViewModel.emulationStarted.collectLatest { |
| 328 | if (it) { | 376 | if (it) { |
| 329 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) | 377 | binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) |
| 330 | ViewUtils.showView(binding.surfaceInputOverlay) | 378 | ViewUtils.showView(binding.surfaceInputOverlay) |
| 331 | ViewUtils.hideView(binding.loadingIndicator) | 379 | ViewUtils.hideView(binding.loadingIndicator) |
| 332 | 380 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt index fa2a4c9f9..5aa3f453f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt | |||
| @@ -21,8 +21,10 @@ import androidx.fragment.app.activityViewModels | |||
| 21 | import androidx.navigation.findNavController | 21 | import androidx.navigation.findNavController |
| 22 | import androidx.navigation.fragment.navArgs | 22 | import androidx.navigation.fragment.navArgs |
| 23 | import com.google.android.material.transition.MaterialSharedAxis | 23 | import com.google.android.material.transition.MaterialSharedAxis |
| 24 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 24 | import org.yuzu.yuzu_emu.R | 25 | import org.yuzu.yuzu_emu.R |
| 25 | import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding | 26 | import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding |
| 27 | import org.yuzu.yuzu_emu.model.GameVerificationResult | ||
| 26 | import org.yuzu.yuzu_emu.model.HomeViewModel | 28 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 27 | import org.yuzu.yuzu_emu.utils.GameMetadata | 29 | import org.yuzu.yuzu_emu.utils.GameMetadata |
| 28 | 30 | ||
| @@ -101,6 +103,38 @@ class GameInfoFragment : Fragment() { | |||
| 101 | """.trimIndent() | 103 | """.trimIndent() |
| 102 | copyToClipboard(args.game.title, details) | 104 | copyToClipboard(args.game.title, details) |
| 103 | } | 105 | } |
| 106 | |||
| 107 | buttonVerifyIntegrity.setOnClickListener { | ||
| 108 | ProgressDialogFragment.newInstance( | ||
| 109 | requireActivity(), | ||
| 110 | R.string.verifying, | ||
| 111 | true | ||
| 112 | ) { progressCallback, _ -> | ||
| 113 | val result = GameVerificationResult.from( | ||
| 114 | NativeLibrary.verifyGameContents( | ||
| 115 | args.game.path, | ||
| 116 | progressCallback | ||
| 117 | ) | ||
| 118 | ) | ||
| 119 | return@newInstance when (result) { | ||
| 120 | GameVerificationResult.Success -> | ||
| 121 | MessageDialogFragment.newInstance( | ||
| 122 | titleId = R.string.verify_success, | ||
| 123 | descriptionId = R.string.operation_completed_successfully | ||
| 124 | ) | ||
| 125 | GameVerificationResult.Failed -> | ||
| 126 | MessageDialogFragment.newInstance( | ||
| 127 | titleId = R.string.verify_failure, | ||
| 128 | descriptionId = R.string.verify_failure_description | ||
| 129 | ) | ||
| 130 | GameVerificationResult.NotImplemented -> | ||
| 131 | MessageDialogFragment.newInstance( | ||
| 132 | titleId = R.string.verify_no_result, | ||
| 133 | descriptionId = R.string.verify_no_result_description | ||
| 134 | ) | ||
| 135 | } | ||
| 136 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) | ||
| 137 | } | ||
| 104 | } | 138 | } |
| 105 | 139 | ||
| 106 | setInsets() | 140 | 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 6ddd758e6..aefae2938 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 | |||
| @@ -32,6 +32,7 @@ import org.yuzu.yuzu_emu.BuildConfig | |||
| 32 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 32 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| 33 | import org.yuzu.yuzu_emu.NativeLibrary | 33 | import org.yuzu.yuzu_emu.NativeLibrary |
| 34 | import org.yuzu.yuzu_emu.R | 34 | import org.yuzu.yuzu_emu.R |
| 35 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 35 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter | 36 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter |
| 36 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding | 37 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding |
| 37 | import org.yuzu.yuzu_emu.features.DocumentProvider | 38 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| @@ -142,6 +143,38 @@ class HomeSettingsFragment : Fragment() { | |||
| 142 | ) | 143 | ) |
| 143 | add( | 144 | add( |
| 144 | HomeSetting( | 145 | HomeSetting( |
| 146 | R.string.verify_installed_content, | ||
| 147 | R.string.verify_installed_content_description, | ||
| 148 | R.drawable.ic_check_circle, | ||
| 149 | { | ||
| 150 | ProgressDialogFragment.newInstance( | ||
| 151 | requireActivity(), | ||
| 152 | titleId = R.string.verifying, | ||
| 153 | cancellable = true | ||
| 154 | ) { progressCallback, _ -> | ||
| 155 | val result = NativeLibrary.verifyInstalledContents(progressCallback) | ||
| 156 | return@newInstance if (result.isEmpty()) { | ||
| 157 | MessageDialogFragment.newInstance( | ||
| 158 | titleId = R.string.verify_success, | ||
| 159 | descriptionId = R.string.operation_completed_successfully | ||
| 160 | ) | ||
| 161 | } else { | ||
| 162 | val failedNames = result.joinToString("\n") | ||
| 163 | val errorMessage = YuzuApplication.appContext.getString( | ||
| 164 | R.string.verification_failed_for, | ||
| 165 | failedNames | ||
| 166 | ) | ||
| 167 | MessageDialogFragment.newInstance( | ||
| 168 | titleId = R.string.verify_failure, | ||
| 169 | descriptionString = errorMessage | ||
| 170 | ) | ||
| 171 | } | ||
| 172 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) | ||
| 173 | } | ||
| 174 | ) | ||
| 175 | ) | ||
| 176 | add( | ||
| 177 | HomeSetting( | ||
| 145 | R.string.share_log, | 178 | R.string.share_log, |
| 146 | R.string.share_log_description, | 179 | R.string.share_log_description, |
| 147 | R.drawable.ic_log, | 180 | R.drawable.ic_log, |
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 32062b6fe..620d8db7c 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 | |||
| @@ -69,7 +69,7 @@ class MessageDialogFragment : DialogFragment() { | |||
| 69 | private const val HELP_LINK = "Link" | 69 | private const val HELP_LINK = "Link" |
| 70 | 70 | ||
| 71 | fun newInstance( | 71 | fun newInstance( |
| 72 | activity: FragmentActivity, | 72 | activity: FragmentActivity? = null, |
| 73 | titleId: Int = 0, | 73 | titleId: Int = 0, |
| 74 | titleString: String = "", | 74 | titleString: String = "", |
| 75 | descriptionId: Int = 0, | 75 | descriptionId: Int = 0, |
| @@ -86,9 +86,11 @@ class MessageDialogFragment : DialogFragment() { | |||
| 86 | putString(DESCRIPTION_STRING, descriptionString) | 86 | putString(DESCRIPTION_STRING, descriptionString) |
| 87 | putInt(HELP_LINK, helpLinkId) | 87 | putInt(HELP_LINK, helpLinkId) |
| 88 | } | 88 | } |
| 89 | ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { | 89 | if (activity != null) { |
| 90 | clear() | 90 | ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { |
| 91 | this.positiveAction = positiveAction | 91 | clear() |
| 92 | this.positiveAction = positiveAction | ||
| 93 | } | ||
| 92 | } | 94 | } |
| 93 | dialog.arguments = bundle | 95 | dialog.arguments = bundle |
| 94 | return dialog | 96 | return dialog |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameVerificationResult.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameVerificationResult.kt new file mode 100644 index 000000000..804637fb8 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameVerificationResult.kt | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | enum class GameVerificationResult(val int: Int) { | ||
| 7 | Success(0), | ||
| 8 | Failed(1), | ||
| 9 | NotImplemented(2); | ||
| 10 | |||
| 11 | companion object { | ||
| 12 | fun from(int: Int): GameVerificationResult = | ||
| 13 | entries.firstOrNull { it.int == int } ?: Success | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 559ae83eb..cf93304da 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h | |||
| @@ -63,6 +63,7 @@ struct Values { | |||
| 63 | Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay", | 63 | Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay", |
| 64 | Settings::Category::Overlay}; | 64 | Settings::Category::Overlay}; |
| 65 | Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay}; | 65 | Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay}; |
| 66 | Settings::Setting<s32> lock_drawer{linkage, false, "lock_drawer", Settings::Category::Overlay}; | ||
| 66 | }; | 67 | }; |
| 67 | 68 | ||
| 68 | extern Values values; | 69 | extern Values values; |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index be0a723b1..963f57380 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -829,6 +829,43 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, | |||
| 829 | program_id, GetJString(env, jname)); | 829 | program_id, GetJString(env, jname)); |
| 830 | } | 830 | } |
| 831 | 831 | ||
| 832 | jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env, jobject jobj, | ||
| 833 | jobject jcallback) { | ||
| 834 | auto jlambdaClass = env->GetObjectClass(jcallback); | ||
| 835 | auto jlambdaInvokeMethod = env->GetMethodID( | ||
| 836 | jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | ||
| 837 | const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) { | ||
| 838 | auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod, | ||
| 839 | ToJDouble(env, max), ToJDouble(env, progress)); | ||
| 840 | return GetJBoolean(env, jwasCancelled); | ||
| 841 | }; | ||
| 842 | |||
| 843 | auto& session = EmulationSession::GetInstance(); | ||
| 844 | std::vector<std::string> result = ContentManager::VerifyInstalledContents( | ||
| 845 | &session.System(), session.GetContentProvider(), callback); | ||
| 846 | jobjectArray jresult = | ||
| 847 | env->NewObjectArray(result.size(), IDCache::GetStringClass(), ToJString(env, "")); | ||
| 848 | for (size_t i = 0; i < result.size(); ++i) { | ||
| 849 | env->SetObjectArrayElement(jresult, i, ToJString(env, result[i])); | ||
| 850 | } | ||
| 851 | return jresult; | ||
| 852 | } | ||
| 853 | |||
| 854 | jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobject jobj, | ||
| 855 | jstring jpath, jobject jcallback) { | ||
| 856 | auto jlambdaClass = env->GetObjectClass(jcallback); | ||
| 857 | auto jlambdaInvokeMethod = env->GetMethodID( | ||
| 858 | jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | ||
| 859 | const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) { | ||
| 860 | auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod, | ||
| 861 | ToJDouble(env, max), ToJDouble(env, progress)); | ||
| 862 | return GetJBoolean(env, jwasCancelled); | ||
| 863 | }; | ||
| 864 | auto& session = EmulationSession::GetInstance(); | ||
| 865 | return static_cast<jint>( | ||
| 866 | ContentManager::VerifyGameContents(&session.System(), GetJString(env, jpath), callback)); | ||
| 867 | } | ||
| 868 | |||
| 832 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, | 869 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, |
| 833 | jstring jprogramId) { | 870 | jstring jprogramId) { |
| 834 | auto program_id = EmulationSession::GetProgramId(env, jprogramId); | 871 | auto program_id = EmulationSession::GetProgramId(env, jprogramId); |
diff --git a/src/android/app/src/main/res/drawable/ic_lock.xml b/src/android/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 000000000..ef97b1936 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_lock.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:height="24dp" | ||
| 3 | android:viewportHeight="24" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:width="24dp"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/layout/fragment_game_info.xml b/src/android/app/src/main/res/layout/fragment_game_info.xml index 80ede8a8c..53af15787 100644 --- a/src/android/app/src/main/res/layout/fragment_game_info.xml +++ b/src/android/app/src/main/res/layout/fragment_game_info.xml | |||
| @@ -118,6 +118,14 @@ | |||
| 118 | android:layout_marginTop="16dp" | 118 | android:layout_marginTop="16dp" |
| 119 | android:text="@string/copy_details" /> | 119 | android:text="@string/copy_details" /> |
| 120 | 120 | ||
| 121 | <com.google.android.material.button.MaterialButton | ||
| 122 | android:id="@+id/button_verify_integrity" | ||
| 123 | style="@style/Widget.Material3.Button" | ||
| 124 | android:layout_width="wrap_content" | ||
| 125 | android:layout_height="wrap_content" | ||
| 126 | android:layout_marginTop="10dp" | ||
| 127 | android:text="@string/verify_integrity" /> | ||
| 128 | |||
| 121 | </LinearLayout> | 129 | </LinearLayout> |
| 122 | 130 | ||
| 123 | </androidx.core.widget.NestedScrollView> | 131 | </androidx.core.widget.NestedScrollView> |
diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml index ac6ab06ff..eecb0563b 100644 --- a/src/android/app/src/main/res/menu/menu_in_game.xml +++ b/src/android/app/src/main/res/menu/menu_in_game.xml | |||
| @@ -22,6 +22,11 @@ | |||
| 22 | android:title="@string/emulation_input_overlay" /> | 22 | android:title="@string/emulation_input_overlay" /> |
| 23 | 23 | ||
| 24 | <item | 24 | <item |
| 25 | android:id="@+id/menu_lock_drawer" | ||
| 26 | android:icon="@drawable/ic_unlock" | ||
| 27 | android:title="@string/emulation_input_overlay" /> | ||
| 28 | |||
| 29 | <item | ||
| 25 | android:id="@+id/menu_exit" | 30 | android:id="@+id/menu_exit" |
| 26 | android:icon="@drawable/ic_exit" | 31 | android:icon="@drawable/ic_exit" |
| 27 | android:title="@string/emulation_exit" /> | 32 | android:title="@string/emulation_exit" /> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index bfcbb5812..779eb36a8 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -142,6 +142,8 @@ | |||
| 142 | <item quantity="other">Successfully imported %d saves</item> | 142 | <item quantity="other">Successfully imported %d saves</item> |
| 143 | </plurals> | 143 | </plurals> |
| 144 | <string name="no_save_data_found">No save data found</string> | 144 | <string name="no_save_data_found">No save data found</string> |
| 145 | <string name="verify_installed_content">Verify installed content</string> | ||
| 146 | <string name="verify_installed_content_description">Checks all installed content for corruption</string> | ||
| 145 | 147 | ||
| 146 | <!-- Applet launcher strings --> | 148 | <!-- Applet launcher strings --> |
| 147 | <string name="applets">Applet launcher</string> | 149 | <string name="applets">Applet launcher</string> |
| @@ -288,6 +290,7 @@ | |||
| 288 | <string name="import_complete">Import complete</string> | 290 | <string name="import_complete">Import complete</string> |
| 289 | <string name="more_options">More options</string> | 291 | <string name="more_options">More options</string> |
| 290 | <string name="use_global_setting">Use global setting</string> | 292 | <string name="use_global_setting">Use global setting</string> |
| 293 | <string name="operation_completed_successfully">The operation completed successfully</string> | ||
| 291 | 294 | ||
| 292 | <!-- GPU driver installation --> | 295 | <!-- GPU driver installation --> |
| 293 | <string name="select_gpu_driver">Select GPU driver</string> | 296 | <string name="select_gpu_driver">Select GPU driver</string> |
| @@ -352,6 +355,14 @@ | |||
| 352 | <string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string> | 355 | <string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string> |
| 353 | <string name="confirm_uninstall">Confirm uninstall</string> | 356 | <string name="confirm_uninstall">Confirm uninstall</string> |
| 354 | <string name="confirm_uninstall_description">Are you sure you want to uninstall this addon?</string> | 357 | <string name="confirm_uninstall_description">Are you sure you want to uninstall this addon?</string> |
| 358 | <string name="verify_integrity">Verify integrity</string> | ||
| 359 | <string name="verifying">Verifying…</string> | ||
| 360 | <string name="verify_success">Integrity verification succeeded!</string> | ||
| 361 | <string name="verify_failure">Integrity verification failed!</string> | ||
| 362 | <string name="verify_failure_description">File contents may be corrupt</string> | ||
| 363 | <string name="verify_no_result">Integrity verification couldn\'t be performed</string> | ||
| 364 | <string name="verify_no_result_description">File contents were not checked for validity</string> | ||
| 365 | <string name="verification_failed_for">Verification failed for the following files:\n%1$s</string> | ||
| 355 | 366 | ||
| 356 | <!-- ROM loading errors --> | 367 | <!-- ROM loading errors --> |
| 357 | <string name="loader_error_encrypted">Your ROM is encrypted</string> | 368 | <string name="loader_error_encrypted">Your ROM is encrypted</string> |
| @@ -381,6 +392,8 @@ | |||
| 381 | <string name="emulation_unpause">Unpause emulation</string> | 392 | <string name="emulation_unpause">Unpause emulation</string> |
| 382 | <string name="emulation_input_overlay">Overlay options</string> | 393 | <string name="emulation_input_overlay">Overlay options</string> |
| 383 | <string name="touchscreen">Touchscreen</string> | 394 | <string name="touchscreen">Touchscreen</string> |
| 395 | <string name="lock_drawer">Lock drawer</string> | ||
| 396 | <string name="unlock_drawer">Unlock drawer</string> | ||
| 384 | 397 | ||
| 385 | <string name="load_settings">Loading settings…</string> | 398 | <string name="load_settings">Loading settings…</string> |
| 386 | 399 | ||
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index 23f2979db..0b0fee73e 100644 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h | |||
| @@ -11,10 +11,12 @@ | |||
| 11 | #include "core/file_sys/content_archive.h" | 11 | #include "core/file_sys/content_archive.h" |
| 12 | #include "core/file_sys/mode.h" | 12 | #include "core/file_sys/mode.h" |
| 13 | #include "core/file_sys/nca_metadata.h" | 13 | #include "core/file_sys/nca_metadata.h" |
| 14 | #include "core/file_sys/patch_manager.h" | ||
| 14 | #include "core/file_sys/registered_cache.h" | 15 | #include "core/file_sys/registered_cache.h" |
| 15 | #include "core/file_sys/submission_package.h" | 16 | #include "core/file_sys/submission_package.h" |
| 16 | #include "core/hle/service/filesystem/filesystem.h" | 17 | #include "core/hle/service/filesystem/filesystem.h" |
| 17 | #include "core/loader/loader.h" | 18 | #include "core/loader/loader.h" |
| 19 | #include "core/loader/nca.h" | ||
| 18 | 20 | ||
| 19 | namespace ContentManager { | 21 | namespace ContentManager { |
| 20 | 22 | ||
| @@ -25,6 +27,12 @@ enum class InstallResult { | |||
| 25 | BaseInstallAttempted, | 27 | BaseInstallAttempted, |
| 26 | }; | 28 | }; |
| 27 | 29 | ||
| 30 | enum class GameVerificationResult { | ||
| 31 | Success, | ||
| 32 | Failed, | ||
| 33 | NotImplemented, | ||
| 34 | }; | ||
| 35 | |||
| 28 | /** | 36 | /** |
| 29 | * \brief Removes a single installed DLC | 37 | * \brief Removes a single installed DLC |
| 30 | * \param fs_controller [FileSystemController] reference from the Core::System instance | 38 | * \param fs_controller [FileSystemController] reference from the Core::System instance |
| @@ -119,14 +127,14 @@ inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_contro | |||
| 119 | * \param system Raw pointer to the system instance | 127 | * \param system Raw pointer to the system instance |
| 120 | * \param vfs Raw pointer to the VfsFilesystem instance in Core::System | 128 | * \param vfs Raw pointer to the VfsFilesystem instance in Core::System |
| 121 | * \param filename Path to the NSP file | 129 | * \param filename Path to the NSP file |
| 122 | * \param callback Optional callback to report the progress of the installation. The first size_t | 130 | * \param callback Callback to report the progress of the installation. The first size_t |
| 123 | * parameter is the total size of the virtual file and the second is the current progress. If you | 131 | * parameter is the total size of the virtual file and the second is the current progress. If you |
| 124 | * return false to the callback, it will cancel the installation as soon as possible. | 132 | * return true to the callback, it will cancel the installation as soon as possible. |
| 125 | * \return [InstallResult] representing how the installation finished | 133 | * \return [InstallResult] representing how the installation finished |
| 126 | */ | 134 | */ |
| 127 | inline InstallResult InstallNSP( | 135 | inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vfs, |
| 128 | Core::System* system, FileSys::VfsFilesystem* vfs, const std::string& filename, | 136 | const std::string& filename, |
| 129 | const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | 137 | const std::function<bool(size_t, size_t)>& callback) { |
| 130 | const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | 138 | const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, |
| 131 | std::size_t block_size) { | 139 | std::size_t block_size) { |
| 132 | if (src == nullptr || dest == nullptr) { | 140 | if (src == nullptr || dest == nullptr) { |
| @@ -184,15 +192,15 @@ inline InstallResult InstallNSP( | |||
| 184 | * \param filename Path to the NCA file | 192 | * \param filename Path to the NCA file |
| 185 | * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to | 193 | * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to |
| 186 | * \param title_type Type of NCA package to install | 194 | * \param title_type Type of NCA package to install |
| 187 | * \param callback Optional callback to report the progress of the installation. The first size_t | 195 | * \param callback Callback to report the progress of the installation. The first size_t |
| 188 | * parameter is the total size of the virtual file and the second is the current progress. If you | 196 | * parameter is the total size of the virtual file and the second is the current progress. If you |
| 189 | * return false to the callback, it will cancel the installation as soon as possible. | 197 | * return true to the callback, it will cancel the installation as soon as possible. |
| 190 | * \return [InstallResult] representing how the installation finished | 198 | * \return [InstallResult] representing how the installation finished |
| 191 | */ | 199 | */ |
| 192 | inline InstallResult InstallNCA( | 200 | inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& filename, |
| 193 | FileSys::VfsFilesystem* vfs, const std::string& filename, | 201 | FileSys::RegisteredCache* registered_cache, |
| 194 | FileSys::RegisteredCache* registered_cache, const FileSys::TitleType title_type, | 202 | const FileSys::TitleType title_type, |
| 195 | const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | 203 | const std::function<bool(size_t, size_t)>& callback) { |
| 196 | const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | 204 | const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, |
| 197 | std::size_t block_size) { | 205 | std::size_t block_size) { |
| 198 | if (src == nullptr || dest == nullptr) { | 206 | if (src == nullptr || dest == nullptr) { |
| @@ -235,4 +243,129 @@ inline InstallResult InstallNCA( | |||
| 235 | } | 243 | } |
| 236 | } | 244 | } |
| 237 | 245 | ||
| 246 | /** | ||
| 247 | * \brief Verifies the installed contents for a given ManualContentProvider | ||
| 248 | * \param system Raw pointer to the system instance | ||
| 249 | * \param provider Raw pointer to the content provider that's tracking indexed games | ||
| 250 | * \param callback Callback to report the progress of the installation. The first size_t | ||
| 251 | * parameter is the total size of the installed contents and the second is the current progress. If | ||
| 252 | * you return true to the callback, it will cancel the installation as soon as possible. | ||
| 253 | * \return A list of entries that failed to install. Returns an empty vector if successful. | ||
| 254 | */ | ||
| 255 | inline std::vector<std::string> VerifyInstalledContents( | ||
| 256 | Core::System* system, FileSys::ManualContentProvider* provider, | ||
| 257 | const std::function<bool(size_t, size_t)>& callback) { | ||
| 258 | // Get content registries. | ||
| 259 | auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | ||
| 260 | auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||
| 261 | |||
| 262 | std::vector<FileSys::RegisteredCache*> content_providers; | ||
| 263 | if (bis_contents) { | ||
| 264 | content_providers.push_back(bis_contents); | ||
| 265 | } | ||
| 266 | if (user_contents) { | ||
| 267 | content_providers.push_back(user_contents); | ||
| 268 | } | ||
| 269 | |||
| 270 | // Get associated NCA files. | ||
| 271 | std::vector<FileSys::VirtualFile> nca_files; | ||
| 272 | |||
| 273 | // Get all installed IDs. | ||
| 274 | size_t total_size = 0; | ||
| 275 | for (auto nca_provider : content_providers) { | ||
| 276 | const auto entries = nca_provider->ListEntriesFilter(); | ||
| 277 | |||
| 278 | for (const auto& entry : entries) { | ||
| 279 | auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||
| 280 | if (!nca_file) { | ||
| 281 | continue; | ||
| 282 | } | ||
| 283 | |||
| 284 | total_size += nca_file->GetSize(); | ||
| 285 | nca_files.push_back(std::move(nca_file)); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | // Declare a list of file names which failed to verify. | ||
| 290 | std::vector<std::string> failed; | ||
| 291 | |||
| 292 | size_t processed_size = 0; | ||
| 293 | bool cancelled = false; | ||
| 294 | auto nca_callback = [&](size_t nca_processed, size_t nca_total) { | ||
| 295 | cancelled = callback(total_size, processed_size + nca_processed); | ||
| 296 | return !cancelled; | ||
| 297 | }; | ||
| 298 | |||
| 299 | // Using the NCA loader, determine if all NCAs are valid. | ||
| 300 | for (auto& nca_file : nca_files) { | ||
| 301 | Loader::AppLoader_NCA nca_loader(nca_file); | ||
| 302 | |||
| 303 | auto status = nca_loader.VerifyIntegrity(nca_callback); | ||
| 304 | if (cancelled) { | ||
| 305 | break; | ||
| 306 | } | ||
| 307 | if (status != Loader::ResultStatus::Success) { | ||
| 308 | FileSys::NCA nca(nca_file); | ||
| 309 | const auto title_id = nca.GetTitleId(); | ||
| 310 | std::string title_name = "unknown"; | ||
| 311 | |||
| 312 | const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||
| 313 | FileSys::ContentRecordType::Control); | ||
| 314 | if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||
| 315 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||
| 316 | *provider}; | ||
| 317 | const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||
| 318 | if (nacp) { | ||
| 319 | title_name = nacp->GetApplicationName(); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | if (title_id > 0) { | ||
| 324 | failed.push_back( | ||
| 325 | fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||
| 326 | } else { | ||
| 327 | failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | processed_size += nca_file->GetSize(); | ||
| 332 | } | ||
| 333 | return failed; | ||
| 334 | } | ||
| 335 | |||
| 336 | /** | ||
| 337 | * \brief Verifies the contents of a given game | ||
| 338 | * \param system Raw pointer to the system instance | ||
| 339 | * \param game_path Patch to the game file | ||
| 340 | * \param callback Callback to report the progress of the installation. The first size_t | ||
| 341 | * parameter is the total size of the installed contents and the second is the current progress. If | ||
| 342 | * you return true to the callback, it will cancel the installation as soon as possible. | ||
| 343 | * \return GameVerificationResult representing how the verification process finished | ||
| 344 | */ | ||
| 345 | inline GameVerificationResult VerifyGameContents( | ||
| 346 | Core::System* system, const std::string& game_path, | ||
| 347 | const std::function<bool(size_t, size_t)>& callback) { | ||
| 348 | const auto loader = Loader::GetLoader( | ||
| 349 | *system, system->GetFilesystem()->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 350 | if (loader == nullptr) { | ||
| 351 | return GameVerificationResult::NotImplemented; | ||
| 352 | } | ||
| 353 | |||
| 354 | bool cancelled = false; | ||
| 355 | auto loader_callback = [&](size_t processed, size_t total) { | ||
| 356 | cancelled = callback(total, processed); | ||
| 357 | return !cancelled; | ||
| 358 | }; | ||
| 359 | |||
| 360 | const auto status = loader->VerifyIntegrity(loader_callback); | ||
| 361 | if (cancelled || status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||
| 362 | return GameVerificationResult::NotImplemented; | ||
| 363 | } | ||
| 364 | |||
| 365 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 366 | return GameVerificationResult::Failed; | ||
| 367 | } | ||
| 368 | return GameVerificationResult::Success; | ||
| 369 | } | ||
| 370 | |||
| 238 | } // namespace ContentManager | 371 | } // namespace ContentManager |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 05bd4174c..d8b0beadf 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -2786,16 +2786,6 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | |||
| 2786 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), | 2786 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), |
| 2787 | tr("File contents were not checked for validity.")); | 2787 | tr("File contents were not checked for validity.")); |
| 2788 | }; | 2788 | }; |
| 2789 | const auto Failed = [this] { | ||
| 2790 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2791 | tr("File contents may be corrupt.")); | ||
| 2792 | }; | ||
| 2793 | |||
| 2794 | const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 2795 | if (loader == nullptr) { | ||
| 2796 | NotImplemented(); | ||
| 2797 | return; | ||
| 2798 | } | ||
| 2799 | 2789 | ||
| 2800 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | 2790 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); |
| 2801 | progress.setWindowModality(Qt::WindowModal); | 2791 | progress.setWindowModality(Qt::WindowModal); |
| @@ -2803,30 +2793,26 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | |||
| 2803 | progress.setAutoClose(false); | 2793 | progress.setAutoClose(false); |
| 2804 | progress.setAutoReset(false); | 2794 | progress.setAutoReset(false); |
| 2805 | 2795 | ||
| 2806 | const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { | 2796 | const auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { |
| 2807 | if (progress.wasCanceled()) { | ||
| 2808 | return false; | ||
| 2809 | } | ||
| 2810 | |||
| 2811 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | 2797 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); |
| 2812 | return true; | 2798 | return progress.wasCanceled(); |
| 2813 | }; | 2799 | }; |
| 2814 | 2800 | ||
| 2815 | const auto status = loader->VerifyIntegrity(QtProgressCallback); | 2801 | const auto result = |
| 2816 | if (progress.wasCanceled() || | 2802 | ContentManager::VerifyGameContents(system.get(), game_path, QtProgressCallback); |
| 2817 | status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | 2803 | progress.close(); |
| 2804 | switch (result) { | ||
| 2805 | case ContentManager::GameVerificationResult::Success: | ||
| 2806 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2807 | tr("The operation completed successfully.")); | ||
| 2808 | break; | ||
| 2809 | case ContentManager::GameVerificationResult::Failed: | ||
| 2810 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2811 | tr("File contents may be corrupt.")); | ||
| 2812 | break; | ||
| 2813 | case ContentManager::GameVerificationResult::NotImplemented: | ||
| 2818 | NotImplemented(); | 2814 | NotImplemented(); |
| 2819 | return; | ||
| 2820 | } | ||
| 2821 | |||
| 2822 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 2823 | Failed(); | ||
| 2824 | return; | ||
| 2825 | } | 2815 | } |
| 2826 | |||
| 2827 | progress.close(); | ||
| 2828 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2829 | tr("The operation completed successfully.")); | ||
| 2830 | } | 2816 | } |
| 2831 | 2817 | ||
| 2832 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | 2818 | void GMainWindow::OnGameListCopyTID(u64 program_id) { |
| @@ -4121,10 +4107,6 @@ void GMainWindow::OnOpenYuzuFolder() { | |||
| 4121 | } | 4107 | } |
| 4122 | 4108 | ||
| 4123 | void GMainWindow::OnVerifyInstalledContents() { | 4109 | void GMainWindow::OnVerifyInstalledContents() { |
| 4124 | // Declare sizes. | ||
| 4125 | size_t total_size = 0; | ||
| 4126 | size_t processed_size = 0; | ||
| 4127 | |||
| 4128 | // Initialize a progress dialog. | 4110 | // Initialize a progress dialog. |
| 4129 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | 4111 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); |
| 4130 | progress.setWindowModality(Qt::WindowModal); | 4112 | progress.setWindowModality(Qt::WindowModal); |
| @@ -4132,93 +4114,25 @@ void GMainWindow::OnVerifyInstalledContents() { | |||
| 4132 | progress.setAutoClose(false); | 4114 | progress.setAutoClose(false); |
| 4133 | progress.setAutoReset(false); | 4115 | progress.setAutoReset(false); |
| 4134 | 4116 | ||
| 4135 | // Declare a list of file names which failed to verify. | ||
| 4136 | std::vector<std::string> failed; | ||
| 4137 | |||
| 4138 | // Declare progress callback. | 4117 | // Declare progress callback. |
| 4139 | auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) { | 4118 | auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { |
| 4140 | if (progress.wasCanceled()) { | 4119 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); |
| 4141 | return false; | 4120 | return progress.wasCanceled(); |
| 4142 | } | ||
| 4143 | progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size)); | ||
| 4144 | return true; | ||
| 4145 | }; | 4121 | }; |
| 4146 | 4122 | ||
| 4147 | // Get content registries. | 4123 | const std::vector<std::string> result = |
| 4148 | auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | 4124 | ContentManager::VerifyInstalledContents(system.get(), provider.get(), QtProgressCallback); |
| 4149 | auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||
| 4150 | |||
| 4151 | std::vector<FileSys::RegisteredCache*> content_providers; | ||
| 4152 | if (bis_contents) { | ||
| 4153 | content_providers.push_back(bis_contents); | ||
| 4154 | } | ||
| 4155 | if (user_contents) { | ||
| 4156 | content_providers.push_back(user_contents); | ||
| 4157 | } | ||
| 4158 | |||
| 4159 | // Get associated NCA files. | ||
| 4160 | std::vector<FileSys::VirtualFile> nca_files; | ||
| 4161 | |||
| 4162 | // Get all installed IDs. | ||
| 4163 | for (auto nca_provider : content_providers) { | ||
| 4164 | const auto entries = nca_provider->ListEntriesFilter(); | ||
| 4165 | |||
| 4166 | for (const auto& entry : entries) { | ||
| 4167 | auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||
| 4168 | if (!nca_file) { | ||
| 4169 | continue; | ||
| 4170 | } | ||
| 4171 | |||
| 4172 | total_size += nca_file->GetSize(); | ||
| 4173 | nca_files.push_back(std::move(nca_file)); | ||
| 4174 | } | ||
| 4175 | } | ||
| 4176 | |||
| 4177 | // Using the NCA loader, determine if all NCAs are valid. | ||
| 4178 | for (auto& nca_file : nca_files) { | ||
| 4179 | Loader::AppLoader_NCA nca_loader(nca_file); | ||
| 4180 | |||
| 4181 | auto status = nca_loader.VerifyIntegrity(QtProgressCallback); | ||
| 4182 | if (progress.wasCanceled()) { | ||
| 4183 | break; | ||
| 4184 | } | ||
| 4185 | if (status != Loader::ResultStatus::Success) { | ||
| 4186 | FileSys::NCA nca(nca_file); | ||
| 4187 | const auto title_id = nca.GetTitleId(); | ||
| 4188 | std::string title_name = "unknown"; | ||
| 4189 | |||
| 4190 | const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||
| 4191 | FileSys::ContentRecordType::Control); | ||
| 4192 | if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||
| 4193 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||
| 4194 | *provider}; | ||
| 4195 | const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||
| 4196 | if (nacp) { | ||
| 4197 | title_name = nacp->GetApplicationName(); | ||
| 4198 | } | ||
| 4199 | } | ||
| 4200 | |||
| 4201 | if (title_id > 0) { | ||
| 4202 | failed.push_back( | ||
| 4203 | fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||
| 4204 | } else { | ||
| 4205 | failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||
| 4206 | } | ||
| 4207 | } | ||
| 4208 | |||
| 4209 | processed_size += nca_file->GetSize(); | ||
| 4210 | } | ||
| 4211 | |||
| 4212 | progress.close(); | 4125 | progress.close(); |
| 4213 | 4126 | ||
| 4214 | if (failed.size() > 0) { | 4127 | if (result.empty()) { |
| 4215 | auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n"))); | 4128 | QMessageBox::information(this, tr("Integrity verification succeeded!"), |
| 4129 | tr("The operation completed successfully.")); | ||
| 4130 | } else { | ||
| 4131 | const auto failed_names = | ||
| 4132 | QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); | ||
| 4216 | QMessageBox::critical( | 4133 | QMessageBox::critical( |
| 4217 | this, tr("Integrity verification failed!"), | 4134 | this, tr("Integrity verification failed!"), |
| 4218 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); | 4135 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); |
| 4219 | } else { | ||
| 4220 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 4221 | tr("The operation completed successfully.")); | ||
| 4222 | } | 4136 | } |
| 4223 | } | 4137 | } |
| 4224 | 4138 | ||