summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt54
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameVerificationResult.kt15
-rw-r--r--src/android/app/src/main/jni/android_settings.h1
-rw-r--r--src/android/app/src/main/jni/native.cpp37
-rw-r--r--src/android/app/src/main/res/drawable/ic_lock.xml9
-rw-r--r--src/android/app/src/main/res/layout/fragment_game_info.xml8
-rw-r--r--src/android/app/src/main/res/menu/menu_in_game.xml5
-rw-r--r--src/android/app/src/main/res/values/strings.xml13
-rw-r--r--src/frontend_common/content_manager.h155
-rw-r--r--src/yuzu/main.cpp138
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
23import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 23import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
24import org.yuzu.yuzu_emu.model.InstallResult 24import org.yuzu.yuzu_emu.model.InstallResult
25import org.yuzu.yuzu_emu.model.Patch 25import org.yuzu.yuzu_emu.model.Patch
26import 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
21import androidx.navigation.findNavController 21import androidx.navigation.findNavController
22import androidx.navigation.fragment.navArgs 22import androidx.navigation.fragment.navArgs
23import com.google.android.material.transition.MaterialSharedAxis 23import com.google.android.material.transition.MaterialSharedAxis
24import org.yuzu.yuzu_emu.NativeLibrary
24import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
25import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding 26import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
27import org.yuzu.yuzu_emu.model.GameVerificationResult
26import org.yuzu.yuzu_emu.model.HomeViewModel 28import org.yuzu.yuzu_emu.model.HomeViewModel
27import org.yuzu.yuzu_emu.utils.GameMetadata 29import 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
32import org.yuzu.yuzu_emu.HomeNavigationDirections 32import org.yuzu.yuzu_emu.HomeNavigationDirections
33import org.yuzu.yuzu_emu.NativeLibrary 33import org.yuzu.yuzu_emu.NativeLibrary
34import org.yuzu.yuzu_emu.R 34import org.yuzu.yuzu_emu.R
35import org.yuzu.yuzu_emu.YuzuApplication
35import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter 36import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
36import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
37import org.yuzu.yuzu_emu.features.DocumentProvider 38import 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
4package org.yuzu.yuzu_emu.model
5
6enum 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
68extern Values values; 69extern 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
832jobject 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
854jint 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
832jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, 869jstring 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
19namespace ContentManager { 21namespace ContentManager {
20 22
@@ -25,6 +27,12 @@ enum class InstallResult {
25 BaseInstallAttempted, 27 BaseInstallAttempted,
26}; 28};
27 29
30enum 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 */
127inline InstallResult InstallNSP( 135inline 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 */
192inline InstallResult InstallNCA( 200inline 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 */
255inline 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 */
345inline 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
2832void GMainWindow::OnGameListCopyTID(u64 program_id) { 2818void GMainWindow::OnGameListCopyTID(u64 program_id) {
@@ -4121,10 +4107,6 @@ void GMainWindow::OnOpenYuzuFolder() {
4121} 4107}
4122 4108
4123void GMainWindow::OnVerifyInstalledContents() { 4109void 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