early-access version 4082
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| yuzu emulator early access | ||||
| ============= | ||||
|  | ||||
| This is the source code for early-access 4079. | ||||
| This is the source code for early-access 4082. | ||||
|  | ||||
| ## Legal Notice | ||||
|  | ||||
|   | ||||
| @@ -82,8 +82,8 @@ android { | ||||
|     } | ||||
|  | ||||
|     val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE") | ||||
|     if (keystoreFile != null) { | ||||
|         signingConfigs { | ||||
|     signingConfigs { | ||||
|         if (keystoreFile != null) { | ||||
|             create("release") { | ||||
|                 storeFile = file(keystoreFile) | ||||
|                 storePassword = System.getenv("ANDROID_KEYSTORE_PASS") | ||||
| @@ -91,6 +91,12 @@ android { | ||||
|                 keyPassword = System.getenv("ANDROID_KEYSTORE_PASS") | ||||
|             } | ||||
|         } | ||||
|         create("default") { | ||||
|             storeFile = file("$projectDir/debug.keystore") | ||||
|             storePassword = "android" | ||||
|             keyAlias = "androiddebugkey" | ||||
|             keyPassword = "android" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Define build types, which are orthogonal to product flavors. | ||||
| @@ -101,7 +107,7 @@ android { | ||||
|             signingConfig = if (keystoreFile != null) { | ||||
|                 signingConfigs.getByName("release") | ||||
|             } else { | ||||
|                 signingConfigs.getByName("debug") | ||||
|                 signingConfigs.getByName("default") | ||||
|             } | ||||
|  | ||||
|             resValue("string", "app_name_suffixed", "yuzu") | ||||
| @@ -118,7 +124,7 @@ android { | ||||
|         register("relWithDebInfo") { | ||||
|             isDefault = true | ||||
|             resValue("string", "app_name_suffixed", "yuzu Debug Release") | ||||
|             signingConfig = signingConfigs.getByName("debug") | ||||
|             signingConfig = signingConfigs.getByName("default") | ||||
|             isMinifyEnabled = true | ||||
|             isDebuggable = true | ||||
|             proguardFiles( | ||||
| @@ -133,6 +139,7 @@ android { | ||||
|         // Signed by debug key disallowing distribution on Play Store. | ||||
|         // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. | ||||
|         debug { | ||||
|             signingConfig = signingConfigs.getByName("default") | ||||
|             resValue("string", "app_name_suffixed", "yuzu Debug") | ||||
|             isDebuggable = true | ||||
|             isJniDebuggable = true | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/android/app/debug.keystore
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/android/app/debug.keystore
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.utils.Log | ||||
| import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | ||||
| import org.yuzu.yuzu_emu.model.InstallResult | ||||
| import org.yuzu.yuzu_emu.model.Patch | ||||
| import org.yuzu.yuzu_emu.model.GameVerificationResult | ||||
|  | ||||
| /** | ||||
|  * Class which contains methods that interact | ||||
| @@ -564,6 +565,26 @@ object NativeLibrary { | ||||
|      */ | ||||
|     external fun removeMod(programId: String, name: String) | ||||
|  | ||||
|     /** | ||||
|      * Verifies all installed content | ||||
|      * @param callback UI callback for verification progress. Return true in the callback to cancel. | ||||
|      * @return Array of content that failed verification. Successful if empty. | ||||
|      */ | ||||
|     external fun verifyInstalledContents( | ||||
|         callback: (max: Long, progress: Long) -> Boolean | ||||
|     ): Array<String> | ||||
|  | ||||
|     /** | ||||
|      * Verifies the contents of a game | ||||
|      * @param path String path to a game | ||||
|      * @param callback UI callback for verification progress. Return true in the callback to cancel. | ||||
|      * @return Int that is meant to be converted to a [GameVerificationResult] | ||||
|      */ | ||||
|     external fun verifyGameContents( | ||||
|         path: String, | ||||
|         callback: (max: Long, progress: Long) -> Boolean | ||||
|     ): Int | ||||
|  | ||||
|     /** | ||||
|      * Gets the save location for a specific game | ||||
|      * | ||||
|   | ||||
| @@ -23,7 +23,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { | ||||
|     THEME("theme"), | ||||
|     THEME_MODE("theme_mode"), | ||||
|     OVERLAY_SCALE("control_scale"), | ||||
|     OVERLAY_OPACITY("control_opacity"); | ||||
|     OVERLAY_OPACITY("control_opacity"), | ||||
|     LOCK_DRAWER("lock_drawer"); | ||||
|  | ||||
|     override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) | ||||
|  | ||||
|   | ||||
| @@ -182,11 +182,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | ||||
|             } | ||||
|  | ||||
|             override fun onDrawerOpened(drawerView: View) { | ||||
|                 // No op | ||||
|                 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) | ||||
|             } | ||||
|  | ||||
|             override fun onDrawerClosed(drawerView: View) { | ||||
|                 // No op | ||||
|                 binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) | ||||
|             } | ||||
|  | ||||
|             override fun onDrawerStateChanged(newState: Int) { | ||||
| @@ -196,6 +196,28 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | ||||
|         binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | ||||
|         binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = | ||||
|             game.title | ||||
|  | ||||
|         binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { | ||||
|             val lockMode = IntSetting.LOCK_DRAWER.getInt() | ||||
|             val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { | ||||
|                 R.string.unlock_drawer | ||||
|             } else { | ||||
|                 R.string.lock_drawer | ||||
|             } | ||||
|             val iconId = if (lockMode == DrawerLayout.LOCK_MODE_UNLOCKED) { | ||||
|                 R.drawable.ic_unlock | ||||
|             } else { | ||||
|                 R.drawable.ic_lock | ||||
|             } | ||||
|  | ||||
|             title = getString(titleId) | ||||
|             icon = ResourcesCompat.getDrawable( | ||||
|                 resources, | ||||
|                 iconId, | ||||
|                 requireContext().theme | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         binding.inGameMenu.setNavigationItemSelectedListener { | ||||
|             when (it.itemId) { | ||||
|                 R.id.menu_pause_emulation -> { | ||||
| @@ -242,6 +264,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | ||||
|                     true | ||||
|                 } | ||||
|  | ||||
|                 R.id.menu_lock_drawer -> { | ||||
|                     when (IntSetting.LOCK_DRAWER.getInt()) { | ||||
|                         DrawerLayout.LOCK_MODE_UNLOCKED -> { | ||||
|                             IntSetting.LOCK_DRAWER.setInt(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | ||||
|                             it.title = resources.getString(R.string.unlock_drawer) | ||||
|                             it.icon = ResourcesCompat.getDrawable( | ||||
|                                 resources, | ||||
|                                 R.drawable.ic_lock, | ||||
|                                 requireContext().theme | ||||
|                             ) | ||||
|                         } | ||||
|  | ||||
|                         DrawerLayout.LOCK_MODE_LOCKED_CLOSED -> { | ||||
|                             IntSetting.LOCK_DRAWER.setInt(DrawerLayout.LOCK_MODE_UNLOCKED) | ||||
|                             it.title = resources.getString(R.string.lock_drawer) | ||||
|                             it.icon = ResourcesCompat.getDrawable( | ||||
|                                 resources, | ||||
|                                 R.drawable.ic_unlock, | ||||
|                                 requireContext().theme | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                     NativeConfig.saveGlobalConfig() | ||||
|                     true | ||||
|                 } | ||||
|  | ||||
|                 R.id.menu_exit -> { | ||||
|                     emulationState.stop() | ||||
|                     emulationViewModel.setIsEmulationStopping(true) | ||||
| @@ -326,7 +374,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | ||||
|                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||
|                     emulationViewModel.emulationStarted.collectLatest { | ||||
|                         if (it) { | ||||
|                             binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) | ||||
|                             binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) | ||||
|                             ViewUtils.showView(binding.surfaceInputOverlay) | ||||
|                             ViewUtils.hideView(binding.loadingIndicator) | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,10 @@ import androidx.fragment.app.activityViewModels | ||||
| import androidx.navigation.findNavController | ||||
| import androidx.navigation.fragment.navArgs | ||||
| import com.google.android.material.transition.MaterialSharedAxis | ||||
| import org.yuzu.yuzu_emu.NativeLibrary | ||||
| import org.yuzu.yuzu_emu.R | ||||
| import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding | ||||
| import org.yuzu.yuzu_emu.model.GameVerificationResult | ||||
| import org.yuzu.yuzu_emu.model.HomeViewModel | ||||
| import org.yuzu.yuzu_emu.utils.GameMetadata | ||||
|  | ||||
| @@ -101,6 +103,38 @@ class GameInfoFragment : Fragment() { | ||||
|                 """.trimIndent() | ||||
|                 copyToClipboard(args.game.title, details) | ||||
|             } | ||||
|  | ||||
|             buttonVerifyIntegrity.setOnClickListener { | ||||
|                 ProgressDialogFragment.newInstance( | ||||
|                     requireActivity(), | ||||
|                     R.string.verifying, | ||||
|                     true | ||||
|                 ) { progressCallback, _ -> | ||||
|                     val result = GameVerificationResult.from( | ||||
|                         NativeLibrary.verifyGameContents( | ||||
|                             args.game.path, | ||||
|                             progressCallback | ||||
|                         ) | ||||
|                     ) | ||||
|                     return@newInstance when (result) { | ||||
|                         GameVerificationResult.Success -> | ||||
|                             MessageDialogFragment.newInstance( | ||||
|                                 titleId = R.string.verify_success, | ||||
|                                 descriptionId = R.string.operation_completed_successfully | ||||
|                             ) | ||||
|                         GameVerificationResult.Failed -> | ||||
|                             MessageDialogFragment.newInstance( | ||||
|                                 titleId = R.string.verify_failure, | ||||
|                                 descriptionId = R.string.verify_failure_description | ||||
|                             ) | ||||
|                         GameVerificationResult.NotImplemented -> | ||||
|                             MessageDialogFragment.newInstance( | ||||
|                                 titleId = R.string.verify_no_result, | ||||
|                                 descriptionId = R.string.verify_no_result_description | ||||
|                             ) | ||||
|                     } | ||||
|                 }.show(parentFragmentManager, ProgressDialogFragment.TAG) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         setInsets() | ||||
|   | ||||
| @@ -32,6 +32,7 @@ import org.yuzu.yuzu_emu.BuildConfig | ||||
| import org.yuzu.yuzu_emu.HomeNavigationDirections | ||||
| import org.yuzu.yuzu_emu.NativeLibrary | ||||
| import org.yuzu.yuzu_emu.R | ||||
| import org.yuzu.yuzu_emu.YuzuApplication | ||||
| import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter | ||||
| import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding | ||||
| import org.yuzu.yuzu_emu.features.DocumentProvider | ||||
| @@ -140,6 +141,38 @@ class HomeSettingsFragment : Fragment() { | ||||
|                     } | ||||
|                 ) | ||||
|             ) | ||||
|             add( | ||||
|                 HomeSetting( | ||||
|                     R.string.verify_installed_content, | ||||
|                     R.string.verify_installed_content_description, | ||||
|                     R.drawable.ic_check_circle, | ||||
|                     { | ||||
|                         ProgressDialogFragment.newInstance( | ||||
|                             requireActivity(), | ||||
|                             titleId = R.string.verifying, | ||||
|                             cancellable = true | ||||
|                         ) { progressCallback, _ -> | ||||
|                             val result = NativeLibrary.verifyInstalledContents(progressCallback) | ||||
|                             return@newInstance if (result.isEmpty()) { | ||||
|                                 MessageDialogFragment.newInstance( | ||||
|                                     titleId = R.string.verify_success, | ||||
|                                     descriptionId = R.string.operation_completed_successfully | ||||
|                                 ) | ||||
|                             } else { | ||||
|                                 val failedNames = result.joinToString("\n") | ||||
|                                 val errorMessage = YuzuApplication.appContext.getString( | ||||
|                                     R.string.verification_failed_for, | ||||
|                                     failedNames | ||||
|                                 ) | ||||
|                                 MessageDialogFragment.newInstance( | ||||
|                                     titleId = R.string.verify_failure, | ||||
|                                     descriptionString = errorMessage | ||||
|                                 ) | ||||
|                             } | ||||
|                         }.show(parentFragmentManager, ProgressDialogFragment.TAG) | ||||
|                     } | ||||
|                 ) | ||||
|             ) | ||||
|             add( | ||||
|                 HomeSetting( | ||||
|                     R.string.share_log, | ||||
|   | ||||
| @@ -69,7 +69,7 @@ class MessageDialogFragment : DialogFragment() { | ||||
|         private const val HELP_LINK = "Link" | ||||
|  | ||||
|         fun newInstance( | ||||
|             activity: FragmentActivity, | ||||
|             activity: FragmentActivity? = null, | ||||
|             titleId: Int = 0, | ||||
|             titleString: String = "", | ||||
|             descriptionId: Int = 0, | ||||
| @@ -86,9 +86,11 @@ class MessageDialogFragment : DialogFragment() { | ||||
|                 putString(DESCRIPTION_STRING, descriptionString) | ||||
|                 putInt(HELP_LINK, helpLinkId) | ||||
|             } | ||||
|             ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { | ||||
|                 clear() | ||||
|                 this.positiveAction = positiveAction | ||||
|             if (activity != null) { | ||||
|                 ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { | ||||
|                     clear() | ||||
|                     this.positiveAction = positiveAction | ||||
|                 } | ||||
|             } | ||||
|             dialog.arguments = bundle | ||||
|             return dialog | ||||
|   | ||||
| @@ -0,0 +1,15 @@ | ||||
| // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| package org.yuzu.yuzu_emu.model | ||||
|  | ||||
| enum class GameVerificationResult(val int: Int) { | ||||
|     Success(0), | ||||
|     Failed(1), | ||||
|     NotImplemented(2); | ||||
|  | ||||
|     companion object { | ||||
|         fun from(int: Int): GameVerificationResult = | ||||
|             entries.firstOrNull { it.int == int } ?: Success | ||||
|     } | ||||
| } | ||||
| @@ -63,6 +63,7 @@ struct Values { | ||||
|     Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay", | ||||
|                                                Settings::Category::Overlay}; | ||||
|     Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay}; | ||||
|     Settings::Setting<s32> lock_drawer{linkage, false, "lock_drawer", Settings::Category::Overlay}; | ||||
| }; | ||||
|  | ||||
| extern Values values; | ||||
|   | ||||
| @@ -829,6 +829,43 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, | ||||
|                               program_id, GetJString(env, jname)); | ||||
| } | ||||
|  | ||||
| jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env, jobject jobj, | ||||
|                                                                       jobject jcallback) { | ||||
|     auto jlambdaClass = env->GetObjectClass(jcallback); | ||||
|     auto jlambdaInvokeMethod = env->GetMethodID( | ||||
|         jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | ||||
|     const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) { | ||||
|         auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod, | ||||
|                                                    ToJDouble(env, max), ToJDouble(env, progress)); | ||||
|         return GetJBoolean(env, jwasCancelled); | ||||
|     }; | ||||
|  | ||||
|     auto& session = EmulationSession::GetInstance(); | ||||
|     std::vector<std::string> result = ContentManager::VerifyInstalledContents( | ||||
|         &session.System(), session.GetContentProvider(), callback); | ||||
|     jobjectArray jresult = | ||||
|         env->NewObjectArray(result.size(), IDCache::GetStringClass(), ToJString(env, "")); | ||||
|     for (size_t i = 0; i < result.size(); ++i) { | ||||
|         env->SetObjectArrayElement(jresult, i, ToJString(env, result[i])); | ||||
|     } | ||||
|     return jresult; | ||||
| } | ||||
|  | ||||
| jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobject jobj, | ||||
|                                                               jstring jpath, jobject jcallback) { | ||||
|     auto jlambdaClass = env->GetObjectClass(jcallback); | ||||
|     auto jlambdaInvokeMethod = env->GetMethodID( | ||||
|         jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | ||||
|     const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) { | ||||
|         auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod, | ||||
|                                                    ToJDouble(env, max), ToJDouble(env, progress)); | ||||
|         return GetJBoolean(env, jwasCancelled); | ||||
|     }; | ||||
|     auto& session = EmulationSession::GetInstance(); | ||||
|     return static_cast<jint>( | ||||
|         ContentManager::VerifyGameContents(&session.System(), GetJString(env, jpath), callback)); | ||||
| } | ||||
|  | ||||
| jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, | ||||
|                                                           jstring jprogramId) { | ||||
|     auto program_id = EmulationSession::GetProgramId(env, jprogramId); | ||||
|   | ||||
							
								
								
									
										9
									
								
								src/android/app/src/main/res/drawable/ic_lock.xml
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								src/android/app/src/main/res/drawable/ic_lock.xml
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:height="24dp" | ||||
|     android:viewportHeight="24" | ||||
|     android:viewportWidth="24" | ||||
|     android:width="24dp"> | ||||
|     <path | ||||
|         android:fillColor="?attr/colorControlNormal" | ||||
|         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" /> | ||||
| </vector> | ||||
| @@ -118,6 +118,14 @@ | ||||
|                 android:layout_marginTop="16dp" | ||||
|                 android:text="@string/copy_details" /> | ||||
|  | ||||
|             <com.google.android.material.button.MaterialButton | ||||
|                 android:id="@+id/button_verify_integrity" | ||||
|                 style="@style/Widget.Material3.Button" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="10dp" | ||||
|                 android:text="@string/verify_integrity" /> | ||||
|  | ||||
|         </LinearLayout> | ||||
|  | ||||
|     </androidx.core.widget.NestedScrollView> | ||||
|   | ||||
| @@ -21,6 +21,11 @@ | ||||
|         android:icon="@drawable/ic_controller" | ||||
|         android:title="@string/emulation_input_overlay" /> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/menu_lock_drawer" | ||||
|         android:icon="@drawable/ic_unlock" | ||||
|         android:title="@string/emulation_input_overlay" /> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/menu_exit" | ||||
|         android:icon="@drawable/ic_exit" | ||||
|   | ||||
| @@ -142,6 +142,8 @@ | ||||
|         <item quantity="other">Successfully imported %d saves</item> | ||||
|     </plurals> | ||||
|     <string name="no_save_data_found">No save data found</string> | ||||
|     <string name="verify_installed_content">Verify installed content</string> | ||||
|     <string name="verify_installed_content_description">Checks all installed content for corruption</string> | ||||
|  | ||||
|     <!-- Applet launcher strings --> | ||||
|     <string name="applets">Applet launcher</string> | ||||
| @@ -288,6 +290,7 @@ | ||||
|     <string name="import_complete">Import complete</string> | ||||
|     <string name="more_options">More options</string> | ||||
|     <string name="use_global_setting">Use global setting</string> | ||||
|     <string name="operation_completed_successfully">The operation completed successfully</string> | ||||
|  | ||||
|     <!-- GPU driver installation --> | ||||
|     <string name="select_gpu_driver">Select GPU driver</string> | ||||
| @@ -352,6 +355,14 @@ | ||||
|     <string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string> | ||||
|     <string name="confirm_uninstall">Confirm uninstall</string> | ||||
|     <string name="confirm_uninstall_description">Are you sure you want to uninstall this addon?</string> | ||||
|     <string name="verify_integrity">Verify integrity</string> | ||||
|     <string name="verifying">Verifying…</string> | ||||
|     <string name="verify_success">Integrity verification succeeded!</string> | ||||
|     <string name="verify_failure">Integrity verification failed!</string> | ||||
|     <string name="verify_failure_description">File contents may be corrupt</string> | ||||
|     <string name="verify_no_result">Integrity verification couldn\'t be performed</string> | ||||
|     <string name="verify_no_result_description">File contents were not checked for validity</string> | ||||
|     <string name="verification_failed_for">Verification failed for the following files:\n%1$s</string> | ||||
|  | ||||
|     <!-- ROM loading errors --> | ||||
|     <string name="loader_error_encrypted">Your ROM is encrypted</string> | ||||
| @@ -381,6 +392,8 @@ | ||||
|     <string name="emulation_unpause">Unpause emulation</string> | ||||
|     <string name="emulation_input_overlay">Overlay options</string> | ||||
|     <string name="touchscreen">Touchscreen</string> | ||||
|     <string name="lock_drawer">Lock drawer</string> | ||||
|     <string name="unlock_drawer">Unlock drawer</string> | ||||
|  | ||||
|     <string name="load_settings">Loading settings…</string> | ||||
|  | ||||
|   | ||||
| @@ -284,9 +284,10 @@ void StaticService::Handle_GetClockSnapshotFromSystemClockContext(HLERequestCont | ||||
|     LOG_DEBUG(Service_Time, "called."); | ||||
|  | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     auto clock_type{rp.PopEnum<Service::PSC::Time::TimeType>()}; | ||||
|     [[maybe_unused]] auto alignment{rp.Pop<u32>()}; | ||||
|     auto user_context{rp.PopRaw<Service::PSC::Time::SystemClockContext>()}; | ||||
|     auto network_context{rp.PopRaw<Service::PSC::Time::SystemClockContext>()}; | ||||
|     auto clock_type{rp.PopEnum<Service::PSC::Time::TimeType>()}; | ||||
|  | ||||
|     Service::PSC::Time::ClockSnapshot snapshot{}; | ||||
|     auto res = | ||||
|   | ||||
| @@ -277,9 +277,10 @@ void StaticService::Handle_GetClockSnapshotFromSystemClockContext(HLERequestCont | ||||
|     LOG_DEBUG(Service_Time, "called."); | ||||
|  | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     auto clock_type{rp.PopEnum<TimeType>()}; | ||||
|     [[maybe_unused]] auto alignment{rp.Pop<u32>()}; | ||||
|     auto user_context{rp.PopRaw<SystemClockContext>()}; | ||||
|     auto network_context{rp.PopRaw<SystemClockContext>()}; | ||||
|     auto clock_type{rp.PopEnum<TimeType>()}; | ||||
|  | ||||
|     ClockSnapshot snapshot{}; | ||||
|     auto res = | ||||
|   | ||||
| @@ -11,10 +11,12 @@ | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/mode.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/patch_manager.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/hle/service/filesystem/filesystem.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/loader/nca.h" | ||||
|  | ||||
| namespace ContentManager { | ||||
|  | ||||
| @@ -25,6 +27,12 @@ enum class InstallResult { | ||||
|     BaseInstallAttempted, | ||||
| }; | ||||
|  | ||||
| enum class GameVerificationResult { | ||||
|     Success, | ||||
|     Failed, | ||||
|     NotImplemented, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * \brief Removes a single installed DLC | ||||
|  * \param fs_controller [FileSystemController] reference from the Core::System instance | ||||
| @@ -119,14 +127,14 @@ inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_contro | ||||
|  * \param system Raw pointer to the system instance | ||||
|  * \param vfs Raw pointer to the VfsFilesystem instance in Core::System | ||||
|  * \param filename Path to the NSP file | ||||
|  * \param callback Optional callback to report the progress of the installation. The first size_t | ||||
|  * \param callback Callback to report the progress of the installation. The first size_t | ||||
|  * parameter is the total size of the virtual file and the second is the current progress. If you | ||||
|  * return false to the callback, it will cancel the installation as soon as possible. | ||||
|  * return true to the callback, it will cancel the installation as soon as possible. | ||||
|  * \return [InstallResult] representing how the installation finished | ||||
|  */ | ||||
| inline InstallResult InstallNSP( | ||||
|     Core::System* system, FileSys::VfsFilesystem* vfs, const std::string& filename, | ||||
|     const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||||
| inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vfs, | ||||
|                                 const std::string& filename, | ||||
|                                 const std::function<bool(size_t, size_t)>& callback) { | ||||
|     const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | ||||
|                                  std::size_t block_size) { | ||||
|         if (src == nullptr || dest == nullptr) { | ||||
| @@ -184,15 +192,15 @@ inline InstallResult InstallNSP( | ||||
|  * \param filename Path to the NCA file | ||||
|  * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to | ||||
|  * \param title_type Type of NCA package to install | ||||
|  * \param callback Optional callback to report the progress of the installation. The first size_t | ||||
|  * \param callback Callback to report the progress of the installation. The first size_t | ||||
|  * parameter is the total size of the virtual file and the second is the current progress. If you | ||||
|  * return false to the callback, it will cancel the installation as soon as possible. | ||||
|  * return true to the callback, it will cancel the installation as soon as possible. | ||||
|  * \return [InstallResult] representing how the installation finished | ||||
|  */ | ||||
| inline InstallResult InstallNCA( | ||||
|     FileSys::VfsFilesystem* vfs, const std::string& filename, | ||||
|     FileSys::RegisteredCache* registered_cache, const FileSys::TitleType title_type, | ||||
|     const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||||
| inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& filename, | ||||
|                                 FileSys::RegisteredCache* registered_cache, | ||||
|                                 const FileSys::TitleType title_type, | ||||
|                                 const std::function<bool(size_t, size_t)>& callback) { | ||||
|     const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | ||||
|                                  std::size_t block_size) { | ||||
|         if (src == nullptr || dest == nullptr) { | ||||
| @@ -235,4 +243,129 @@ inline InstallResult InstallNCA( | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * \brief Verifies the installed contents for a given ManualContentProvider | ||||
|  * \param system Raw pointer to the system instance | ||||
|  * \param provider Raw pointer to the content provider that's tracking indexed games | ||||
|  * \param callback Callback to report the progress of the installation. The first size_t | ||||
|  * parameter is the total size of the installed contents and the second is the current progress. If | ||||
|  * you return true to the callback, it will cancel the installation as soon as possible. | ||||
|  * \return A list of entries that failed to install. Returns an empty vector if successful. | ||||
|  */ | ||||
| inline std::vector<std::string> VerifyInstalledContents( | ||||
|     Core::System* system, FileSys::ManualContentProvider* provider, | ||||
|     const std::function<bool(size_t, size_t)>& callback) { | ||||
|     // Get content registries. | ||||
|     auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | ||||
|     auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||||
|  | ||||
|     std::vector<FileSys::RegisteredCache*> content_providers; | ||||
|     if (bis_contents) { | ||||
|         content_providers.push_back(bis_contents); | ||||
|     } | ||||
|     if (user_contents) { | ||||
|         content_providers.push_back(user_contents); | ||||
|     } | ||||
|  | ||||
|     // Get associated NCA files. | ||||
|     std::vector<FileSys::VirtualFile> nca_files; | ||||
|  | ||||
|     // Get all installed IDs. | ||||
|     size_t total_size = 0; | ||||
|     for (auto nca_provider : content_providers) { | ||||
|         const auto entries = nca_provider->ListEntriesFilter(); | ||||
|  | ||||
|         for (const auto& entry : entries) { | ||||
|             auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||||
|             if (!nca_file) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             total_size += nca_file->GetSize(); | ||||
|             nca_files.push_back(std::move(nca_file)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Declare a list of file names which failed to verify. | ||||
|     std::vector<std::string> failed; | ||||
|  | ||||
|     size_t processed_size = 0; | ||||
|     bool cancelled = false; | ||||
|     auto nca_callback = [&](size_t nca_processed, size_t nca_total) { | ||||
|         cancelled = callback(total_size, processed_size + nca_processed); | ||||
|         return !cancelled; | ||||
|     }; | ||||
|  | ||||
|     // Using the NCA loader, determine if all NCAs are valid. | ||||
|     for (auto& nca_file : nca_files) { | ||||
|         Loader::AppLoader_NCA nca_loader(nca_file); | ||||
|  | ||||
|         auto status = nca_loader.VerifyIntegrity(nca_callback); | ||||
|         if (cancelled) { | ||||
|             break; | ||||
|         } | ||||
|         if (status != Loader::ResultStatus::Success) { | ||||
|             FileSys::NCA nca(nca_file); | ||||
|             const auto title_id = nca.GetTitleId(); | ||||
|             std::string title_name = "unknown"; | ||||
|  | ||||
|             const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||||
|                                                     FileSys::ContentRecordType::Control); | ||||
|             if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||||
|                 const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||||
|                                                *provider}; | ||||
|                 const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||||
|                 if (nacp) { | ||||
|                     title_name = nacp->GetApplicationName(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (title_id > 0) { | ||||
|                 failed.push_back( | ||||
|                     fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||||
|             } else { | ||||
|                 failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         processed_size += nca_file->GetSize(); | ||||
|     } | ||||
|     return failed; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * \brief Verifies the contents of a given game | ||||
|  * \param system Raw pointer to the system instance | ||||
|  * \param game_path Patch to the game file | ||||
|  * \param callback Callback to report the progress of the installation. The first size_t | ||||
|  * parameter is the total size of the installed contents and the second is the current progress. If | ||||
|  * you return true to the callback, it will cancel the installation as soon as possible. | ||||
|  * \return GameVerificationResult representing how the verification process finished | ||||
|  */ | ||||
| inline GameVerificationResult VerifyGameContents( | ||||
|     Core::System* system, const std::string& game_path, | ||||
|     const std::function<bool(size_t, size_t)>& callback) { | ||||
|     const auto loader = Loader::GetLoader( | ||||
|         *system, system->GetFilesystem()->OpenFile(game_path, FileSys::Mode::Read)); | ||||
|     if (loader == nullptr) { | ||||
|         return GameVerificationResult::NotImplemented; | ||||
|     } | ||||
|  | ||||
|     bool cancelled = false; | ||||
|     auto loader_callback = [&](size_t processed, size_t total) { | ||||
|         cancelled = callback(total, processed); | ||||
|         return !cancelled; | ||||
|     }; | ||||
|  | ||||
|     const auto status = loader->VerifyIntegrity(loader_callback); | ||||
|     if (cancelled || status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||||
|         return GameVerificationResult::NotImplemented; | ||||
|     } | ||||
|  | ||||
|     if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||||
|         return GameVerificationResult::Failed; | ||||
|     } | ||||
|     return GameVerificationResult::Success; | ||||
| } | ||||
|  | ||||
| } // namespace ContentManager | ||||
|   | ||||
| @@ -2786,16 +2786,6 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | ||||
|         QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), | ||||
|                              tr("File contents were not checked for validity.")); | ||||
|     }; | ||||
|     const auto Failed = [this] { | ||||
|         QMessageBox::critical(this, tr("Integrity verification failed!"), | ||||
|                               tr("File contents may be corrupt.")); | ||||
|     }; | ||||
|  | ||||
|     const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||||
|     if (loader == nullptr) { | ||||
|         NotImplemented(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||||
|     progress.setWindowModality(Qt::WindowModal); | ||||
| @@ -2803,30 +2793,26 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | ||||
|     progress.setAutoClose(false); | ||||
|     progress.setAutoReset(false); | ||||
|  | ||||
|     const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { | ||||
|         if (progress.wasCanceled()) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|     const auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { | ||||
|         progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | ||||
|         return true; | ||||
|         return progress.wasCanceled(); | ||||
|     }; | ||||
|  | ||||
|     const auto status = loader->VerifyIntegrity(QtProgressCallback); | ||||
|     if (progress.wasCanceled() || | ||||
|         status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||||
|         NotImplemented(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||||
|         Failed(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto result = | ||||
|         ContentManager::VerifyGameContents(system.get(), game_path, QtProgressCallback); | ||||
|     progress.close(); | ||||
|     QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||||
|                              tr("The operation completed successfully.")); | ||||
|     switch (result) { | ||||
|     case ContentManager::GameVerificationResult::Success: | ||||
|         QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||||
|                                  tr("The operation completed successfully.")); | ||||
|         break; | ||||
|     case ContentManager::GameVerificationResult::Failed: | ||||
|         QMessageBox::critical(this, tr("Integrity verification failed!"), | ||||
|                               tr("File contents may be corrupt.")); | ||||
|         break; | ||||
|     case ContentManager::GameVerificationResult::NotImplemented: | ||||
|         NotImplemented(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GMainWindow::OnGameListCopyTID(u64 program_id) { | ||||
| @@ -4121,10 +4107,6 @@ void GMainWindow::OnOpenYuzuFolder() { | ||||
| } | ||||
|  | ||||
| void GMainWindow::OnVerifyInstalledContents() { | ||||
|     // Declare sizes. | ||||
|     size_t total_size = 0; | ||||
|     size_t processed_size = 0; | ||||
|  | ||||
|     // Initialize a progress dialog. | ||||
|     QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||||
|     progress.setWindowModality(Qt::WindowModal); | ||||
| @@ -4132,93 +4114,25 @@ void GMainWindow::OnVerifyInstalledContents() { | ||||
|     progress.setAutoClose(false); | ||||
|     progress.setAutoReset(false); | ||||
|  | ||||
|     // Declare a list of file names which failed to verify. | ||||
|     std::vector<std::string> failed; | ||||
|  | ||||
|     // Declare progress callback. | ||||
|     auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) { | ||||
|         if (progress.wasCanceled()) { | ||||
|             return false; | ||||
|         } | ||||
|         progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size)); | ||||
|         return true; | ||||
|     auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { | ||||
|         progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | ||||
|         return progress.wasCanceled(); | ||||
|     }; | ||||
|  | ||||
|     // Get content registries. | ||||
|     auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | ||||
|     auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||||
|  | ||||
|     std::vector<FileSys::RegisteredCache*> content_providers; | ||||
|     if (bis_contents) { | ||||
|         content_providers.push_back(bis_contents); | ||||
|     } | ||||
|     if (user_contents) { | ||||
|         content_providers.push_back(user_contents); | ||||
|     } | ||||
|  | ||||
|     // Get associated NCA files. | ||||
|     std::vector<FileSys::VirtualFile> nca_files; | ||||
|  | ||||
|     // Get all installed IDs. | ||||
|     for (auto nca_provider : content_providers) { | ||||
|         const auto entries = nca_provider->ListEntriesFilter(); | ||||
|  | ||||
|         for (const auto& entry : entries) { | ||||
|             auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||||
|             if (!nca_file) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             total_size += nca_file->GetSize(); | ||||
|             nca_files.push_back(std::move(nca_file)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Using the NCA loader, determine if all NCAs are valid. | ||||
|     for (auto& nca_file : nca_files) { | ||||
|         Loader::AppLoader_NCA nca_loader(nca_file); | ||||
|  | ||||
|         auto status = nca_loader.VerifyIntegrity(QtProgressCallback); | ||||
|         if (progress.wasCanceled()) { | ||||
|             break; | ||||
|         } | ||||
|         if (status != Loader::ResultStatus::Success) { | ||||
|             FileSys::NCA nca(nca_file); | ||||
|             const auto title_id = nca.GetTitleId(); | ||||
|             std::string title_name = "unknown"; | ||||
|  | ||||
|             const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||||
|                                                     FileSys::ContentRecordType::Control); | ||||
|             if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||||
|                 const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||||
|                                                *provider}; | ||||
|                 const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||||
|                 if (nacp) { | ||||
|                     title_name = nacp->GetApplicationName(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (title_id > 0) { | ||||
|                 failed.push_back( | ||||
|                     fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||||
|             } else { | ||||
|                 failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         processed_size += nca_file->GetSize(); | ||||
|     } | ||||
|  | ||||
|     const std::vector<std::string> result = | ||||
|         ContentManager::VerifyInstalledContents(system.get(), provider.get(), QtProgressCallback); | ||||
|     progress.close(); | ||||
|  | ||||
|     if (failed.size() > 0) { | ||||
|         auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n"))); | ||||
|     if (result.empty()) { | ||||
|         QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||||
|                                  tr("The operation completed successfully.")); | ||||
|     } else { | ||||
|         const auto failed_names = | ||||
|             QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); | ||||
|         QMessageBox::critical( | ||||
|             this, tr("Integrity verification failed!"), | ||||
|             tr("Verification failed for the following files:\n\n%1").arg(failed_names)); | ||||
|     } else { | ||||
|         QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||||
|                                  tr("The operation completed successfully.")); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user