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 c408485c6..0fb35bf98 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
@@ -614,6 +614,11 @@ object NativeLibrary {
*/
external fun clearFilesystemProvider()
+ /**
+ * Checks if all necessary keys are present for decryption
+ */
+ external fun areKeysPresent(): Boolean
+
/**
* Button type for use in onTouchEvent
*/
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index 064342cdd..ebf41a639 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -31,6 +31,7 @@ import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.NativeLibrary
import java.io.File
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -162,7 +163,7 @@ class SetupFragment : Fragment() {
R.string.install_prod_keys_warning_help,
{
val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
- if (file.exists()) {
+ if (file.exists() && NativeLibrary.areKeysPresent()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
@@ -347,7 +348,8 @@ class SetupFragment : Fragment() {
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
- if (mainActivity.processKey(result)) {
+ mainActivity.processKey(result)
+ if (NativeLibrary.areKeysPresent()) {
keyCallback.onStepCompleted()
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index 513ac2fc5..cfc777b81 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -31,6 +31,9 @@ class HomeViewModel : ViewModel() {
private val _reloadPropertiesList = MutableStateFlow(false)
val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow()
+ private val _checkKeys = MutableStateFlow(false)
+ val checkKeys = _checkKeys.asStateFlow()
+
var navigatedToSetup = false
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@@ -66,4 +69,8 @@ class HomeViewModel : ViewModel() {
fun reloadPropertiesList(reload: Boolean) {
_reloadPropertiesList.value = reload
}
+
+ fun setCheckKeys(value: Boolean) {
+ _checkKeys.value = value
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index c2cc29961..b3967d294 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -64,6 +64,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
override var themeId: Int = 0
+ private val CHECKED_DECRYPTION = "CheckedDecryption"
+ private var checkedDecryption = false
+
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@@ -75,6 +78,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
+ if (savedInstanceState != null) {
+ checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION)
+ }
+ if (!checkedDecryption) {
+ val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
+ .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
+ if (!firstTimeSetup) {
+ checkKeys()
+ }
+ checkedDecryption = true
+ }
+
WindowCompat.setDecorFitsSystemWindows(window, false)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
@@ -150,6 +165,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
}
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ homeViewModel.checkKeys.collect {
+ if (it) {
+ checkKeys()
+ homeViewModel.setCheckKeys(false)
+ }
+ }
+ }
+ }
}
// Dismiss previous notifications (should not happen unless a crash occurred)
@@ -158,6 +183,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
setInsets()
}
+ private fun checkKeys() {
+ if (!NativeLibrary.areKeysPresent()) {
+ MessageDialogFragment.newInstance(
+ titleId = R.string.keys_missing,
+ descriptionId = R.string.keys_missing_description,
+ helpLinkId = R.string.keys_missing_help
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption)
+ }
+
fun finishSetup(navController: NavController) {
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
@@ -349,6 +389,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
R.string.install_keys_success,
Toast.LENGTH_SHORT
).show()
+ homeViewModel.setCheckKeys(true)
gamesViewModel.reloadGames(true)
return true
} else {
@@ -399,6 +440,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, true)
NativeLibrary.initializeSystem(true)
+ homeViewModel.setCheckKeys(true)
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 4c3644cc5..e51453eca 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -913,4 +913,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env,
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
}
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) {
+ auto& system = EmulationSession::GetInstance().System();
+ system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
+ return ContentManager::AreKeysPresent();
+}
+
} // extern "C"
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 779eb36a8..3cd1586fd 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -144,6 +144,9 @@
No save data found
Verify installed content
Checks all installed content for corruption
+ Encryption keys are missing
+ Firmware and retail games cannot be decrypted
+ https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys
Applet launcher