diff options
8 files changed, 165 insertions, 4 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/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/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/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/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index bfcbb5812..eefcc3ff4 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> |