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 c7d6d6c33a..c0e5983fc6 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 @@ -15,6 +15,7 @@ import android.view.Surface import android.view.View import android.widget.TextView import androidx.annotation.Keep +import androidx.core.net.toUri import com.google.android.material.dialog.MaterialAlertDialogBuilder import net.swiftzer.semver.SemVer import java.lang.ref.WeakReference @@ -27,6 +28,7 @@ import org.yuzu.yuzu_emu.model.InstallResult import org.yuzu.yuzu_emu.model.Patch import org.yuzu.yuzu_emu.model.GameVerificationResult import org.yuzu.yuzu_emu.network.NetPlayManager +import java.io.File /** * Class which contains methods that interact @@ -102,6 +104,21 @@ object NativeLibrary { FileUtil.getFilename(Uri.parse(path)) } + @Keep + @JvmStatic + fun copyFileToStorage(source: String, destdir: String): Boolean { + return FileUtil.copyUriToInternalStorage( + source.toUri(), + destdir + ) != null + } + + @Keep + @JvmStatic + fun getFileExtension(source: String): String { + return FileUtil.getExtension(source.toUri()) + } + external fun setAppDirectory(directory: String) /** @@ -415,18 +432,29 @@ object NativeLibrary { */ external fun firmwareVersion(): String - fun isFirmwareSupported(): Boolean { - var version: SemVer + /** + * Verifies installed firmware. + * + * @return The result code. + */ + external fun verifyFirmware(): Int - try { - version = SemVer.parse(firmwareVersion()) - } catch (_: Exception) { - return false - } - val max = SemVer(19, 0, 1) + /** + * Check if a game requires firmware to be playable. + * + * @param programId The game's Program ID. + * @return Whether or not the game requires firmware to be playable. + */ + external fun gameRequiresFirmware(programId: String): Boolean - return version <= max - } + /** + * Installs keys from the specified path. + * + * @param path The path to install keys from. + * @param ext What extension the keys should have. + * @return The result code. + */ + external fun installKeys(path: String, ext: String): Int /** * Checks the PatchManager for any addons that are available diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 750e8f4729..c4652f55e1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -3,11 +3,16 @@ package org.yuzu.yuzu_emu.adapters +import android.content.DialogInterface import android.net.Uri +import android.text.Html +import android.text.method.LinkMovementMethod import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.ImageView +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.pm.ShortcutInfoCompat @@ -33,6 +38,10 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder import androidx.recyclerview.widget.RecyclerView +import androidx.core.net.toUri +import androidx.core.content.edit +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.NativeLibrary class GameAdapter(private val activity: AppCompatActivity) : AbstractDiffAdapter(exact = false) { @@ -171,8 +180,9 @@ class GameAdapter(private val activity: AppCompatActivity) : fun onClick(game: Game) { val gameExists = DocumentFile.fromSingleUri( YuzuApplication.appContext, - Uri.parse(game.path) + game.path.toUri() )?.exists() == true + if (!gameExists) { Toast.makeText( YuzuApplication.appContext, @@ -184,29 +194,49 @@ class GameAdapter(private val activity: AppCompatActivity) : return } - val preferences = - PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - preferences.edit() - .putLong( - game.keyLastPlayedTime, - System.currentTimeMillis() - ) - .apply() - - activity.lifecycleScope.launch { - withContext(Dispatchers.IO) { - val shortcut = - ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path) - .setShortLabel(game.title) - .setIcon(GameIconUtils.getShortcutIcon(activity, game)) - .setIntent(game.launchIntent) - .build() - ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) + val launch: () -> Unit = { + val preferences = + PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + preferences.edit { + putLong( + game.keyLastPlayedTime, + System.currentTimeMillis() + ) } + + activity.lifecycleScope.launch { + withContext(Dispatchers.IO) { + val shortcut = + ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path) + .setShortLabel(game.title) + .setIcon(GameIconUtils.getShortcutIcon(activity, game)) + .setIntent(game.launchIntent) + .build() + ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) + } + } + + val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true) + binding.root.findNavController().navigate(action) } - val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true) - binding.root.findNavController().navigate(action) + if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.loader_requires_firmware) + .setMessage( + Html.fromHtml( + activity.getString(R.string.loader_requires_firmware_description), + Html.FROM_HTML_MODE_LEGACY + ) + ) + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + launch() + } + .setNegativeButton(android.R.string.cancel) { _,_ -> } + .show() + } else { + launch() + } } fun onLongClick(game: Game): Boolean { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt index 0c1e39d095..91670b207d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt @@ -264,8 +264,6 @@ class DriverFetcherFragment : Fragment() { } releases.add(release) - - println(release.publishTime) } } 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 f39d9514b3..5fed99e0b0 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 @@ -137,7 +137,7 @@ class HomeSettingsFragment : Fragment() { binding.root.findNavController() .navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment) }, - { NativeLibrary.isFirmwareAvailable() && NativeLibrary.isFirmwareSupported() }, + { NativeLibrary.isFirmwareAvailable() }, R.string.applets_error_firmware, R.string.applets_error_description ) 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 6443067885..61797f75f5 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 @@ -352,7 +352,7 @@ class SetupFragment : Fragment() { val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> if (result != null) { - mainActivity.processKey(result) + mainActivity.processKey(result, "keys") if (NativeLibrary.areKeysPresent()) { keyCallback.onStepCompleted() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 1a74057569..43b9085f50 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt @@ -47,9 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.JaroWinkler import java.util.Locale import androidx.core.content.edit -import androidx.core.view.updateLayoutParams -import org.yuzu.yuzu_emu.features.settings.model.Settings -import android.view.ViewParent import androidx.core.view.doOnNextLayout class GamesFragment : Fragment() { @@ -151,7 +148,7 @@ class GamesFragment : Fragment() { ) } gamesViewModel.games.collect(viewLifecycleOwner) { - if (it.size > 0) { + if (it.isNotEmpty()) { setAdapter(it) } } @@ -361,7 +358,7 @@ class GamesFragment : Fragment() { popup.setOnMenuItemClickListener { item -> currentFilter = item.itemId - preferences.edit().putInt(PREF_SORT_TYPE, currentFilter).apply() + preferences.edit { putInt(PREF_SORT_TYPE, currentFilter) } filterAndSearch() true } 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 f3800f94e9..f8ba35dbd5 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 @@ -6,6 +6,8 @@ package org.yuzu.yuzu_emu.ui.main import android.content.Intent import android.net.Uri import android.os.Bundle +import android.os.ParcelFileDescriptor +import android.provider.OpenableColumns import android.view.View import android.view.ViewGroup.MarginLayoutParams import android.view.WindowManager @@ -47,6 +49,7 @@ import java.io.BufferedOutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import androidx.core.content.edit +import androidx.core.net.toFile class MainActivity : AppCompatActivity(), ThemeProvider { private lateinit var binding: ActivityMainBinding @@ -70,10 +73,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider { val granted = permissions.entries.all { it.value } if (granted) { // Permissions were granted. - Toast.makeText(this, R.string.bluetooth_permissions_granted, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.bluetooth_permissions_granted, Toast.LENGTH_SHORT) + .show() } else { // Permissions were denied. - Toast.makeText(this, R.string.bluetooth_permissions_denied, Toast.LENGTH_LONG).show() + Toast.makeText(this, R.string.bluetooth_permissions_denied, Toast.LENGTH_LONG) + .show() } } @@ -94,7 +99,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } } - + override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } @@ -105,13 +110,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider { NativeLibrary.initMultiplayer() binding = ActivityMainBinding.inflate(layoutInflater) - + setContentView(binding.root) - - + + checkAndRequestBluetoothPermissions() - + if (savedInstanceState != null) { checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION) checkedFirmware = savedInstanceState.getBoolean(CHECKED_FIRMWARE) @@ -146,22 +151,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider { binding.statusBarShade.setBackgroundColor( ThemeHelper.getColorWithOpacity( MaterialColors.getColor( - binding.root, - com.google.android.material.R.attr.colorSurface - ), - ThemeHelper.SYSTEM_BAR_ALPHA + binding.root, com.google.android.material.R.attr.colorSurface + ), ThemeHelper.SYSTEM_BAR_ALPHA ) ) - if (InsetsHelper.getSystemGestureType(applicationContext) != - InsetsHelper.GESTURE_NAVIGATION - ) { + if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { binding.navigationBarShade.setBackgroundColor( ThemeHelper.getColorWithOpacity( MaterialColors.getColor( - binding.root, - com.google.android.material.R.attr.colorSurface - ), - ThemeHelper.SYSTEM_BAR_ALPHA + binding.root, com.google.android.material.R.attr.colorSurface + ), ThemeHelper.SYSTEM_BAR_ALPHA ) ) } @@ -172,9 +171,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) } homeViewModel.contentToInstall.collect( - this, - resetState = { homeViewModel.setContentToInstall(null) } - ) { + this, resetState = { homeViewModel.setContentToInstall(null) }) { if (it != null) { installContent(it) } @@ -183,7 +180,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { if (it) checkKeys() } - homeViewModel.checkFirmware.collect(this, resetState = { homeViewModel.setCheckFirmware(false) }) { + homeViewModel.checkFirmware.collect( + this, resetState = { homeViewModel.setCheckFirmware(false) }) { if (it) checkFirmware() } @@ -203,12 +201,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider { negativeButtonTitleId = R.string.close, showNegativeButton = true, positiveAction = { - PreferenceManager.getDefaultSharedPreferences(applicationContext) - .edit() { - putBoolean(Settings.PREF_SHOULD_SHOW_PRE_ALPHA_WARNING, false) - } - } - ).show(supportFragmentManager, MessageDialogFragment.TAG) + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() { + putBoolean(Settings.PREF_SHOULD_SHOW_PRE_ALPHA_WARNING, false) + } + }).show(supportFragmentManager, MessageDialogFragment.TAG) } } @@ -228,15 +224,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } private fun checkFirmware() { - if (!NativeLibrary.isFirmwareAvailable() || !NativeLibrary.isFirmwareSupported()) { - MessageDialogFragment.newInstance( - titleId = R.string.firmware_missing, - descriptionId = R.string.firmware_missing_description, - helpLinkId = R.string.firmware_missing_help - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } - } + val resultCode: Int = NativeLibrary.verifyFirmware() + if (resultCode == 0) return; + val resultString: String = + resources.getStringArray(R.array.verifyFirmwareResults)[resultCode] + + MessageDialogFragment.newInstance( + titleId = R.string.firmware_invalid, + descriptionString = resultString, + helpLinkId = R.string.firmware_missing_help + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -283,23 +282,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider { super.onResume() } - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { _: View, windowInsets: WindowInsetsCompat -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams - mlpStatusShade.height = insets.top - binding.statusBarShade.layoutParams = mlpStatusShade + private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener( + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams + mlpStatusShade.height = insets.top + binding.statusBarShade.layoutParams = mlpStatusShade - // The only situation where we care to have a nav bar shade is when it's at the bottom - // of the screen where scrolling list elements can go behind it. - val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams - mlpNavShade.height = insets.bottom - binding.navigationBarShade.layoutParams = mlpNavShade + // The only situation where we care to have a nav bar shade is when it's at the bottom + // of the screen where scrolling list elements can go behind it. + val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams + mlpNavShade.height = insets.bottom + binding.navigationBarShade.layoutParams = mlpNavShade - windowInsets - } + windowInsets + } override fun setTheme(resId: Int) { super.setTheme(resId) @@ -315,17 +313,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider { fun processGamesDir(result: Uri, calledFromGameFragment: Boolean = false) { contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION + result, Intent.FLAG_GRANT_READ_URI_PERMISSION ) val uriString = result.toString() val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString } if (folder != null) { Toast.makeText( - applicationContext, - R.string.folder_already_added, - Toast.LENGTH_SHORT + applicationContext, R.string.folder_already_added, Toast.LENGTH_SHORT ).show() return } @@ -334,72 +329,50 @@ class MainActivity : AppCompatActivity(), ThemeProvider { .show(supportFragmentManager, AddGameFolderDialogFragment.TAG) } - val getProdKey = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result != null) { - processKey(result) - } + val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result != null) { + processKey(result, "keys") } - - fun processKey(result: Uri): Boolean { - if (FileUtil.getExtension(result) != "keys") { - MessageDialogFragment.newInstance( - this, - titleId = R.string.reading_keys_failure, - descriptionId = R.string.install_prod_keys_failure_extension_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return false - } - - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) - - val dstPath = DirectoryInitialization.userDirectory + "/keys/" - if (FileUtil.copyUriToInternalStorage( - result, - dstPath, - "prod.keys" - ) != null - ) { - if (NativeLibrary.reloadKeys()) { - Toast.makeText( - applicationContext, - R.string.install_keys_success, - Toast.LENGTH_SHORT - ).show() - homeViewModel.setCheckKeys(true) - - val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) - if (!firstTimeSetup) { - homeViewModel.setCheckFirmware(true) - } - - gamesViewModel.reloadGames(true) - return true - } else { - MessageDialogFragment.newInstance( - this, - titleId = R.string.invalid_keys_error, - descriptionId = R.string.install_keys_failure_description, - helpLinkId = R.string.dumping_keys_quickstart_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return false - } - } - return false } - val getFirmware = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult - } + val getAmiiboKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result != null) { + processKey(result, "bin") + } + } + fun processKey(result: Uri, extension: String = "keys") { + contentResolver.takePersistableUriPermission( + result, Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + + val resultCode: Int = NativeLibrary.installKeys(result.toString(), extension); + + if (resultCode == 0) { + Toast.makeText( + applicationContext, R.string.keys_install_success, Toast.LENGTH_SHORT + ).show() + + gamesViewModel.reloadGames(true) + + return + } + + val resultString: String = + resources.getStringArray(R.array.installKeysResults)[resultCode] + + MessageDialogFragment.newInstance( + titleId = R.string.keys_failed, + descriptionString = resultString, + helpLinkId = R.string.keys_missing_help + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } + + val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result != null) { processFirmware(result) } + } fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) { val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } @@ -409,15 +382,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider { val cacheFirmwareDir = File("${cacheDir.path}/registered/") ProgressDialogFragment.newInstance( - this, - R.string.firmware_installing + this, R.string.firmware_installing ) { progressCallback, _ -> var messageToShow: Any try { FileUtil.unzipToInternalStorage( - result.toString(), - cacheFirmwareDir, - progressCallback + result.toString(), cacheFirmwareDir, progressCallback ) val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 @@ -448,10 +418,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } fun uninstallFirmware() { - val firmwarePath = File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") + val firmwarePath = + File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") ProgressDialogFragment.newInstance( - this, - R.string.firmware_uninstalling + this, R.string.firmware_uninstalling ) { progressCallback, _ -> var messageToShow: Any try { @@ -473,49 +443,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { messageToShow }.show(supportFragmentManager, ProgressDialogFragment.TAG) } - val getAmiiboKey = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult - } - - if (FileUtil.getExtension(result) != "bin") { - MessageDialogFragment.newInstance( - this, - titleId = R.string.reading_keys_failure, - descriptionId = R.string.install_amiibo_keys_failure_extension_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return@registerForActivityResult - } - - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) - - val dstPath = DirectoryInitialization.userDirectory + "/keys/" - if (FileUtil.copyUriToInternalStorage( - result, - dstPath, - "key_retail.bin" - ) != null - ) { - if (NativeLibrary.reloadKeys()) { - Toast.makeText( - applicationContext, - R.string.install_keys_success, - Toast.LENGTH_SHORT - ).show() - } else { - MessageDialogFragment.newInstance( - this, - titleId = R.string.invalid_keys_error, - descriptionId = R.string.install_keys_failure_description, - helpLinkId = R.string.dumping_keys_quickstart_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } - } - } val installGameUpdate = registerForActivityResult( ActivityResultContracts.OpenMultipleDocuments() @@ -530,15 +457,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } ProgressDialogFragment.newInstance( - this@MainActivity, - R.string.verifying_content, - false + this@MainActivity, R.string.verifying_content, false ) { _, _ -> var updatesMatchProgram = true for (document in documents) { val valid = NativeLibrary.doesUpdateMatchProgram( - addonViewModel.game!!.programId, - document.toString() + addonViewModel.game!!.programId, document.toString() ) if (!valid) { updatesMatchProgram = false @@ -554,16 +478,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider { titleId = R.string.content_install_notice, descriptionId = R.string.content_install_notice_description, positiveAction = { homeViewModel.setContentToInstall(documents) }, - negativeAction = {} - ) + negativeAction = {}) } }.show(supportFragmentManager, ProgressDialogFragment.TAG) } private fun installContent(documents: List) { ProgressDialogFragment.newInstance( - this@MainActivity, - R.string.installing_game_content + this@MainActivity, R.string.installing_game_content ) { progressCallback, messageCallback -> var installSuccess = 0 var installOverwrite = 0 @@ -571,14 +493,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider { var error = 0 documents.forEach { messageCallback.invoke(FileUtil.getFilename(it)) - when ( - InstallResult.from( - NativeLibrary.installFileToNand( - it.toString(), - progressCallback - ) + when (InstallResult.from( + NativeLibrary.installFileToNand( + it.toString(), progressCallback ) - ) { + )) { InstallResult.Success -> { installSuccess += 1 } @@ -599,13 +518,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider { addonViewModel.refreshAddons() - val separator = System.getProperty("line.separator") ?: "\n" + val separator = System.lineSeparator() ?: "\n" val installResult = StringBuilder() if (installSuccess > 0) { installResult.append( getString( - R.string.install_game_content_success_install, - installSuccess + R.string.install_game_content_success_install, installSuccess ) ) installResult.append(separator) @@ -613,8 +531,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { if (installOverwrite > 0) { installResult.append( getString( - R.string.install_game_content_success_overwrite, - installOverwrite + R.string.install_game_content_success_overwrite, installOverwrite ) ) installResult.append(separator) @@ -624,8 +541,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { installResult.append(separator) installResult.append( getString( - R.string.install_game_content_failed_count, - errorTotal + R.string.install_game_content_failed_count, errorTotal ) ) installResult.append(separator) @@ -666,9 +582,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } ProgressDialogFragment.newInstance( - this, - R.string.exporting_user_data, - true + this, R.string.exporting_user_data, true ) { progressCallback, _ -> val zipResult = FileUtil.zipFromInternalStorage( File(DirectoryInitialization.userDirectory!!), @@ -692,8 +606,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } ProgressDialogFragment.newInstance( - this, - R.string.importing_user_data + this, R.string.importing_user_data ) { progressCallback, _ -> val checkStream = ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index fe7c0658d4..9fed0b1449 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -60,6 +60,7 @@ #include "core/hle/service/set/system_settings_server.h" #include "core/loader/loader.h" #include "frontend_common/config.h" +#include "frontend_common/firmware_manager.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "hid_core/hid_types.h" @@ -283,6 +284,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string : Service::AM::LaunchType::ApplicationInitiated, .program_index = static_cast(program_index), }; + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); if (m_load_result != Core::SystemResultStatus::Success) { return m_load_result; @@ -764,35 +766,19 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass cl } bool isFirmwarePresent() { - auto bis_system = - EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - return false; - } - - // Query an applet to see if it's available - auto applet_nca = - bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program); - if (!applet_nca) { - return false; - } - return true; + return FirmwareManager::CheckFirmwarePresence(EmulationSession::GetInstance().System()); } jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) { return isFirmwarePresent(); } -// TODO(crueter): This check is nonfunctional... jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclass clazz) { - Service::Set::FirmwareVersionFormat firmware_data{}; - const auto result = Service::Set::GetFirmwareVersionImpl( - firmware_data, EmulationSession::GetInstance().System(), - Service::Set::GetFirmwareVersionType::Version2); + const auto pair = FirmwareManager::GetFirmwareVersion(EmulationSession::GetInstance().System()); + const auto firmware_data = pair.first; + const auto result = pair.second; if (result.IsError() || !isFirmwarePresent()) { - LOG_INFO(Frontend, "Installed firmware: No firmware available"); - return Common::Android::ToJString(env, "N/A"); } @@ -804,6 +790,23 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclas return Common::Android::ToJString(env, display_version); } +jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyFirmware(JNIEnv* env, jclass clazz) { + return static_cast(FirmwareManager::VerifyFirmware(EmulationSession::GetInstance().System())); +} + +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) { + auto program_id = EmulationSession::GetProgramId(env, jprogramId); + + return FirmwareManager::GameRequiresFirmware(program_id); +} + +jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz, jstring jpath, jstring jext) { + const auto path = Common::Android::GetJString(env, jpath); + const auto ext = Common::Android::GetJString(env, jext); + + return static_cast(FirmwareManager::InstallKeys(path, ext)); +} + jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj, jstring jpath, jstring jprogramId) { diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index 4ea8b30c1e..e100bafe7e 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -349,13 +349,7 @@ إلغاء تثبيت مفاتيح أميبو مطلوب لاستخدام أميبو في اللعبة - تم تحديد ملف مفاتيح غير صالح - تم تثبيت المفاتيح بنجاح - خطأ في قراءة مفاتيح التشفير - وحاول مرة أخر keys تحقق من أن ملف المفاتيح له امتداد وحاول مرة أخر bin تحقق من أن ملف المفاتيح له امتداد - مفاتيح التشفير غير صالحة - الملف المحدد غير صحيح أو تالف. يرجى إعادة المفاتيح الخاصة بك GPU مدير برنامج تشغيل GPU تثبيت برنامج تشغيل قم بتثبيت برامج تشغيل بديلة للحصول على أداء أو دقة أفضل @@ -422,8 +416,6 @@ فحص المحتوى المثبت بحثًا عن تلف مفاتيح التشفير مفقودة لا يمكن فك تشفير البرنامج الثابت والألعاب - البرنامج الثابت مفقود أو جديد جدًا - بعض الألعاب قد لا تعمل بشكل صحيح. يتطلب إيدن البرنامج الثابت 19.0.1 أو أقل. Qlaunch diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index af4f2056b3..d13f24d173 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -344,13 +344,7 @@ ڕەتکردنەوە دامەزراندنی کلیلی Amiibo پێویستە بۆ بەکارهێنانی Amiibo لە یاریدا - فایلی کلیلێکی نادروست هەڵبژێردرا - کلیلەکان بە سەرکەوتوویی دامەزران - هەڵە لە خوێندنەوەی کۆدکردنی کلیل - دڵنیابەوە کە فایلی کلیلەکانت درێژکراوەی .keys ی هەیە و دووبارە هەوڵبدەرەوە. دڵنیابە کە فایلی کلیلەکانت درێژکراوەی .bin ی هەیە و دووبارە هەوڵبدەرەوە. - کلیلی کۆدکردنی نادروستە - فایلە هەڵبژێردراوەکە هەڵەیە یان تێکچووە. تکایە دووبارە کلیلەکانت دەربێنەوە. دامەزراندنی وەگەڕخەری GPU دامەزراندنی وەگەڕخەری بەدیل بۆ ئەوەی بە ئەگەرێکی زۆرەوە کارایی باشتر یان وردبینی هەبێت ڕێکخستنە پێشکەوتووەکان @@ -416,8 +410,6 @@ هەموو ناوەڕۆکی دامەزراو پشکنین دەکات بۆ تێکچوون کلیلە کۆدکراوەکان دیار نییە پتەوواڵا و یارییە تاکەکەسییەکان ناتوانرێت کۆد بکرێنەوە - فریموێر بوونی نییە یان زۆر نوێە - هەندێ یاری لەوانەیە باش کار نەکەن. ئێدەن پێویستی بە فریموێری ١٩.٠.١ یان کەمترە. Qlaunch diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index c71ea42f75..fe8575512e 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -333,10 +333,6 @@ Zrušit Instalovat Amiibo klíče Povinné použití Amiibo ve hře - Vybrané klíče jsou neplatné - Klíče úspěšně nainstalovány - Chyba při čtení šifrovacích klíčů - Neplatné šifrovací klíče Správce ovladače GPU Instalovat GPU ovladač Pokročilé nastavení @@ -380,8 +376,6 @@ Kontrola poškození obsahu Chybí šifrovací klíče Firmware a retail hry nelze dešifrovat - Firmware chybí nebo je příliš nový - Některé hry nemusí fungovat správně. Eden vyžaduje firmware 19.0.1 nebo nižší. Qlaunch diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 06b14f7c15..93881eabb5 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -346,13 +346,7 @@ Abbrechen Amiibo-Schlüssel installieren Benötigt um Amiibos im Spiel zu verwenden - Ungültige Schlüsseldatei ausgewählt - Schlüssel erfolgreich installiert - Fehler beim Lesen der Schlüssel - Überprüfen Sie, ob Ihre Schlüsseldatei die Erweiterung \".keys\" hat, und versuchen Sie es erneut. Überprüfen Sie, ob Ihre Schlüsseldatei die Erweiterung \".bin\" hat, und versuchen Sie es erneut. - Ungültige Schlüssel - Die ausgewählte Datei ist falsch oder beschädigt. Bitte kopieren Sie Ihre Schlüssel erneut. GPU-Treiber Verwaltung GPU-Treiber installieren Alternative Treiber für eventuell bessere Leistung oder Genauigkeit installieren @@ -415,8 +409,6 @@ Wirklich fortfahren? Überprüft installierte Inhalte auf Fehler Schlüssel fehlen Firmware und Spiele können nicht entschlüsselt werden - Firmware fehlt oder ist zu neu - Einige Spiele funktionieren möglicherweise nicht richtig. Eden erfordert Firmware 19.0.1 oder älter. Qlaunch diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 9cac800975..a55bf4d935 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -347,13 +347,7 @@ Cancelar Instalar claves de Amiibo Necesario para usar Amiibos en el juego - Archivo de claves seleccionado no válido - Claves instaladas correctamente - Error al leer las claves de cifrado - Compruebe que el archivo de claves tenga una extensión .keys y pruebe otra vez. Compruebe que el archivo de claves tenga una extensión .bin y pruebe otra vez. - Claves de cifrado no válidas - El archivo seleccionado es incorrecto o está corrupto. Vuelva a redumpear sus claves. Explorador de drivers de GPU Instalar driver de GPU Instale drivers alternativos para obtener un rendimiento o una precisión potencialmente mejores @@ -428,8 +422,6 @@ Comprueba todo el contenido instalado por si hubiese alguno corrupto Faltan las claves de encriptación El firmware y los juegos no se pueden desencriptar - Falta el firmware o es demasiado nuevo - Algunos juegos pueden no funcionar correctamente. Eden requiere firmware 19.0.1 o inferior. Qlaunch diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index 48f024322b..99f48c1c9d 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -347,13 +347,7 @@ لغو کلیدهای Amiibo را نصب کنید برای استفاده از Amiibo در بازی لازم است - فایل کلیدهای نامعتبر انتخاب شد - کلیدها با موفقیت نصب شدند - خطا در خواندن کلیدهای رمزگذاری - بررسی کنید که فایل کلیدهای شما دارای پسوند keys. باشد و دوباره امتحان کنید. بررسی کنید که فایل کلیدهای شما دارای پسوند bin. باشد و دوباره امتحان کنید. - کلیدهای رمزگذاری نامعتبر - فایل انتخابی نادرست یا خراب است. لطفا کلیدهای خود را دوباره استخراج کنید. مدیریت درایور پردازنده گرافیکی نصب درایور پردازنده گرافیکی درایورهای جایگزین را برای عملکرد یا دقت بهتر نصب کنید @@ -426,8 +420,6 @@ تمام محتوای نصب شده را از نظر خرابی بررسی می‌کند کلیدهای رمزگذاری وجود ندارند ثابت‌افزار و بازی‌های فروشگاهی قابل رمزگشایی نیستند - فریمور وجود ندارد یا خیلی جدید است - برخی بازی‌ها ممکن است به درستی کار نکنند. ایدن به فریمور نسخه 19.0.1 یا پایین‌تر نیاز دارد. Qlaunch diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index f8ace84f1f..d9783766fe 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -347,13 +347,7 @@ Annuler Installer les clés Amiibo Nécessaire pour utiliser les Amiibo en jeu - Fichier de clés sélectionné invalide - Clés installées avec succès - Erreur lors de la lecture des clés de chiffrement - Vérifiez que votre fichier de clés a une extension .keys et réessayez. Vérifiez que votre fichier de clés a une extension .bin et réessayez. - Clés de chiffrement invalides - Le fichier sélectionné est incorrect ou corrompu. Veuillez dumper à nouveau vos clés. Gestionnaire de pilotes du GPU Installer le pilote du GPU Installer des pilotes alternatifs pour des performances ou une précision potentiellement meilleures @@ -428,8 +422,6 @@ Vérifie l\'intégrité des contenus installés Les clés de chiffrement sont manquantes. Le firmware et les jeux commerciaux ne peuvent pas être déchiffrés - Firmware manquant ou trop récent - Certains jeux peuvent ne pas fonctionner correctement. Eden nécessite le firmware 19.0.1 ou antérieur. Qlaunch diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index 711b71d3ed..47a01d7dbd 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -348,13 +348,7 @@ ביטול התקן מפתחות Amiibo נחוץ כדי להשתמש ב Amiibo במשחק - קובץ מפתחות לא חוקי נבחר - מפתחות הותקנו בהצלחה - שגיאה בקריאת מפתחות ההצפנה - ודא שלקובץ המפתחות שלך יש סיומת של key. ונסה/י שוב. ודא/י שלקובץ המפתחות שלך יש סיומת של bin. ונסה/י שוב. - מפתחות הצפנה לא חוקיים - קבוץ שנבחר מושחת או לא נכון. בבקשה הוצא מחדש את המפתחות שלך. מנהל הדרייברים של המעבד הגרפי התקן דרייבר למעבד הגרפי התקן דרייברים אחרים בשביל סיכוי לביצועים או דיוק גבוההים יותר @@ -427,8 +421,6 @@ בודק תוכן מותקן לשגיאות מפתחות הצפנה חסרים לא ניתן לפענח firmware ומשחקים - קושחה חסרה או חדשה מדי - חלק מהמשחקים עשויים לא לפעול כראוי. Eden דורש קושחה בגרסה 19.0.1 או נמוכה יותר. Qlaunch diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index 0bedc72e29..e14af511c6 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -346,13 +346,7 @@ Mégse Amiibo kulcsok telepítése Amiibo használata szükséges a játékhoz - Érvénytelen titkosítófájlok kiválasztva - Kulcsok sikeresen telepítve - Hiba történt a titkosítókulcsok olvasása során - Győződj meg róla, hogy a titkosító fájlod .keys kiterjesztéssel rendelkezik, majd próbáld újra. Győződj meg róla, hogy a titkosító fájlod .bin kiterjesztéssel rendelkezik, majd próbáld újra. - Érvénytelen titkosítókulcsok - A kiválasztott fájl helytelen, vagy sérült. Állíts össze egy új kulcsot. GPU illesztőprogram-kezelő GPU illesztőprogram telepítése Alternatív illesztőprogramok telepítése az esetlegesen elérhető teljesítmény és pontosság érdekében @@ -424,8 +418,6 @@ A telepített tartalom épségét ellenőrzi Hiányzó titkosítókulcsok A Firmware és a kiskereskedelmi (retail) játékok nem dekódolhatók - Hiányzó vagy túl új firmware - Néhány játék nem fog megfelelően működni. Az Eden 19.0.1 vagy régebbi firmware-t igényel. Qlaunch diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index 542734e451..2d931d2c5d 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -347,13 +347,7 @@ Batalkan Install Amiibo keys Diperlukan untuk menggunakan Amiibo di dalam game - Keys yang dipilih invalid - Keys berhasil diinstal - Error saat mengecek enkripsi keys - Pastikan file keys anda memiliki format .keys dan coba lagi. Pastikan file keys anda memiliki format .bin dan coba lagi. - Keys enkripsi tidak valid - File yang dipilih salah atau rusak. Silakan masukkan kembali kunci Anda. Manajer driver GPU Install driver GPU Instal driver lain untuk kinerja atau akurasi yang berpotensi lebih baik @@ -424,8 +418,6 @@ Memeriksa semua konten yang terinstal dari kerusakan Kunci enkripsi hilang Firmware dan game retail tidak dapat didekripsi - Firmware hilang atau terlalu baru - Beberapa game mungkin tidak berfungsi dengan baik. Eden memerlukan firmware 19.0.1 atau lebih rendah. Qlaunch diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index ad5b9f62cf..33a937ca93 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -348,13 +348,7 @@ Annulla Installa le chiavi degli Amiibo Necessario per usare gli Amiibo in gioco - Selezionate chiavi non valide - Chiavi installate correttamente - Errore durante la lettura delle chiavi di crittografia - Controlla che le tue chiavi abbiano l\'estensione .keys e prova di nuovo. Controlla che le tue chiavi abbiano l\'estensione .bin e prova di nuovo - Chiavi di crittografia non valide - Il file selezionato è incorretto o corrotto. Per favore riesegui il dump delle tue chiavi. Gestore driver GPU Installa i driver GPU Installa driver alternativi per potenziali prestazioni migliori o accuratezza. @@ -427,8 +421,6 @@ Verifica l\'integrità di tutti i contenuti installati. Chiavi di crittografia mancanti Impossibile decifrare firmware e giochi retail - Firmware mancante o troppo recente - Alcuni giochi potrebbero non funzionare correttamente. Eden richiede il firmware 19.0.1 o precedente. Qlaunch diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index b209943135..c8d23cb6b9 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -346,13 +346,7 @@ キャンセル Amiibo ゲーム内での Amiibo の使用に必要です - 無効なキーファイルです - 正常にインストールされました - 暗号化キーの読み込み失敗 - キーの拡張子が.keysであることを確認し、再度お試しください。 キーの拡張子が.binであることを確認し、再度お試しください。 - 暗号化キーが無効 - ファイルが間違っているか破損しています。キーを再ダンプしてください。 GPUドライバーの管理 GPUドライバー 代替ドライバーをインストールしてパフォーマンスや精度を向上させます @@ -417,8 +411,6 @@ すべてのインストール済みコンテンツの整合性を確認 暗号化キーが不足 ファームウェアと製品版ゲームを復号化できません - ファームウェアがないか、バージョンが新しすぎます - 一部のゲームが正常に動作しない可能性があります。Edenは19.0.1以下のファームウェアが必要です。 Qlaunch diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 595795570f..deb901fc3f 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -346,13 +346,7 @@ 취소 amiibo 키 설치 게임에서 amiibo 사용 시 필요 - 잘못된 키 파일이 선택됨 - 키 값을 설치했습니다. - 암호화 키 읽기 오류 - 키 파일의 확장자가 .keys인지 확인하고 다시 시도하세요. 키 파일의 확장자가 .bin인지 확인하고 다시 시도하세요. - 암호화 키가 올바르지 않음 - 선택한 파일이 잘못되었거나 손상되었습니다. 키를 다시 덤프하세요. GPU 드라이버 관리자 GPU 드라이버 설치 잠재적인 성능 또는 정확도 개선을 위해 대체 드라이버 설치 @@ -423,8 +417,6 @@ 전체 설치된 콘텐츠의 손상을 확인합니다. 암호화 키를 찾을 수 없음 펌웨어 및 패키지 게임을 해독할 수 없음 - 펌웨어가 없거나 버전이 너무 높습니다 - 일부 게임이 제대로 작동하지 않을 수 있습니다. Eden은 19.0.1 이하 버전의 펌웨어가 필요합니다. Qlaunch diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index 82a4601e65..80173a357d 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -344,13 +344,7 @@ Avbryt Installer Amiibo-nøkler Kreves for å bruke Amiibo i spillet - Ugyldig nøkkelfil valgt - Nøkler vellykket installert - Feil ved lesing av krypteringsnøkler - Kontroller at nøkkelfilen har filtypen .keys, og prøv igjen. Kontroller at nøkkelfilen har filtypen .bin, og prøv igjen. - Ugyldige krypteringsnøkler - Den valgte filen er feil eller ødelagt. Vennligst dump nøklene på nytt. Installer GPU-driver Installer alternative drivere for potensielt bedre ytelse eller nøyaktighet. Avanserte innstillinger @@ -416,8 +410,6 @@ Sjekk for korrupsjon Nøkler mangler Kan ikke dekryptere firmware/spill - Firmware mangler eller er for ny - Noen spill fungerer kanskje ikke skikkelig. Eden krever firmware 19.0.1 eller lavere. Qlaunch diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index 394de5fb16..4742f795c8 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -344,13 +344,7 @@ Anuluj Zainstaluj klucze Amiibo Wymagane aby korzystać z Amiibo w grze - Wybrano niepoprawne klucze - Klucze zainstalowane pomyślnie - Błąd podczas odczytu kluczy - Upewnij się że twoje klucze mają rozszerzenie .keys i spróbuj ponownie. Upewnij się że twoje klucze mają rozszerzenie .bin i spróbuj ponownie. - Niepoprawne klucze - Wybrany plik jest niepoprawny lub uszkodzony. Zrzuć ponownie swoje klucze. Zainstaluj sterownik GPU Użyj alternatywnych sterowników aby potencjalnie zwiększyć wydajność i naprawić błędy Ustawienia zaawansowane @@ -416,8 +410,6 @@ Sprawdza integralność zainstalowanych plików. Brak kluczy Firmware i gry nie mogą być odszyfrowane. - Brak firmware lub zbyt nowa wersja - Niektóre gry mogą nie działać poprawnie. Eden wymaga firmware w wersji 19.0.1 lub starszej. Qlaunch diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 46778754ee..a886450e47 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -347,13 +347,7 @@ Cancelar Instalar chaves Amiibo Necessárias para usar Amiibos em um jogo - Arquivo de chaves selecionado inválido - Chaves instaladas com sucesso - Erro ao ler chaves de encriptação - Verifique se seu arquivo de chaves possui a extensão .keys e tente novamente. Verifique se seu arquivo de chaves possui a extensão .bin e tente novamente. - Chaves de encriptação inválidas - O arquivo selecionado está incorreto ou corrompido. Por favor extraia suas chaves novamente. Gerenciador de driver de GPU Instalar driver para GPU Instale drivers alternativos para desempenho ou precisão potencialmente melhores @@ -428,8 +422,6 @@ Verifica todo o conteúdo instalado em busca de dados corrompidos Faltando chaves de encriptação O firmware e jogos comerciais não poderão ser decriptados - Firmware ausente ou muito recente - Alguns jogos podem não funcionar corretamente. O Eden requer firmware 19.0.1 ou inferior. Qlaunch diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 2da6518d48..caf7090993 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -347,13 +347,7 @@ Cancelar Instala chaves Amiibo Necessário para usares Amiibo no jogo - Ficheiro de chaves inválido - Chaves instaladas com sucesso - Erro ao ler chaves de encriptação - Verifique se seu arquivo keys possui a extensão .keys e tente novamente. Verifique se seu arquivo keys possui a extensão .bin e tente novamente. - Chaves de encriptação inválidas - O ficheiro selecionado está corrompido. Por favor recarrega as tuas chaves. Gerenciador de driver de GPU Instala driver para GPU Instala drivers alternativos para desempenho ou precisão potencialmente melhores @@ -428,8 +422,6 @@ Verifica todo o conteúdo instalado em busca de dados corrompidos Faltando chaves de encriptação O firmware e jogos comerciais não poderão ser decriptados - Firmware em falta ou demasiado recente - Alguns jogos podem não funcionar corretamente. O Eden requer firmware versão 19.0.1 ou inferior. Qlaunch diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index b9f146aa84..3d8371324f 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -346,13 +346,7 @@ Отмена Установить ключи Amiibo Необходимо для использования Amiibo в играх - Выбран неверный файл ключей - Ключи успешно установлены - Ошибка при чтении ключей шифрования - Убедитесь, что файл ключей имеет расширение .keys, и повторите попытку. Убедитесь, что файл ключей имеет расширение .bin, и повторите попытку. - Неверные ключи шифрования - Выбранный файл неверен или поврежден. Пожалуйста, пере-дампите ваши ключи. Менеджер драйверов ГП Установить драйвер ГП Установите альтернативные драйверы для потенциально лучшей производительности и/или точности @@ -430,8 +424,6 @@ Проверяет весь установленный контент на наличие повреждений Отсутствуют ключи шифрования Прошивка и розничные игры не могут быть расшифрованы - Прошивка отсутствует или слишком новая - Некоторые игры могут работать некорректно. Eden требует прошивку версии 19.0.1 или ниже. Qlaunch diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index 0217819b68..d133a470b2 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -298,13 +298,7 @@ Отказати Инсталирајте Амиибо Кеис Потребно је користити Амиибо у игри - Изабрана је неважећа датотека тастера - Кључеви су успешно инсталирани - Грешка приликом чишћења кључева за шифровање - Проверите да датотека кључева има. Екеис Ектенсион и покушајте поново. Проверите да датотека кључева има .бин екстензију и покушајте поново. - Неважеће кључеве за шифровање - Изабрана датотека је нетачна или оштећена. Молим вас да вам умањите кључеве. ГПУ возач фетцхер ГПУ управљач возача Инсталирајте ГПУ драјвер @@ -380,8 +374,6 @@ Шифра о шифрирањима недостају Фирмваре и малопродајне игре не могу се дешифровати - Фирмвер недостаје или је превише нов - Неке игре можда неће радити исправно. Eden захтева фирмвер верзије 19.0.1 или старији. Клаунцх diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index c2ab22b0d4..91b600eded 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -344,12 +344,6 @@ Відміна Встановити ключі Amiibo Необхідно для використання Amiibo в іграх - Вибрано неправильний файл ключів - Ключі успішно встановлено - Помилка під час зчитування ключів шифрування - Переконайтеся, що файл ключів має розширення .keys, і повторіть спробу. - Невірні ключі шифрування - Обраний файл невірний або пошкоджений. Будь ласка, зробіть повторний дамп ваших ключів. Встановити драйвер GPU Встановіть альтернативні драйвери для потенційно кращої продуктивності та/або точності Розширені налаштування @@ -415,8 +409,6 @@ Перевіряє встановлений вміст на наявність помилок. Відсутні ключі Прошивку та роздрібні ігри не вдасться розшифрувати. - Прошивка відсутня або занадто нова - Деякі ігри можуть працювати неправильно. Eden вимагає прошивку версії 19.0.1 або нижче. Qlaunch diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index 75a797a386..87de8e80dc 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -344,13 +344,7 @@ Huỷ Cài đặt Amiibo Cần thiết để dùng Amiibo trong trò chơi - Chìa khóa không hợp lệ - Cài đặt chìa khóa thành công - Lỗi đọc keys mã hóa - Xác minh rằng tệp keys của bạn có đuôi .keys và thử lại. Xác minh rằng tệp keys của bạn có đuôi .bin và thử lại. - Keys mã hoá không hợp lệ - Chọn file sai hoặc bị hỏng. Hãy xuất chìa khóa khác Cài đặt driver GPU Cài đặt driver thay thế để có thể có hiệu suất tốt và chính xác hơn Cài đặt nâng cao @@ -416,8 +410,6 @@ Kiểm tra lỗi nội dung đã cài Thiếu keys mã hóa Không thể giải mã firmware và game - Thiếu firmware hoặc phiên bản quá mới - Một số trò chơi có thể không hoạt động bình thường. Eden yêu cầu firmware phiên bản 19.0.1 hoặc thấp hơn. Qlaunch diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index 534b2a322d..20f9bdea90 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -346,13 +346,7 @@ 取消 安装 Amiibo 密钥文件 在遊戏中使用 Amiibo 时必需 - 选择的密钥文件无效 - 密钥文件已成功安装 - 读取加密密钥时出错 - 请确保您的密钥文件扩展名为 .keys 并重试。 请确保您的密钥文件扩展名为 .bin 并重试。 - 无效的加密密钥 - 选择的密钥文件不正确或已损坏。请重新转储密钥文件。 GPU 驱动管理器 安装 GPU 驱动 安装替代的驱动程序以获得更好的性能和精度 @@ -423,8 +417,6 @@ 检查所有安装的内容是否有损坏 密钥缺失 无法解密固件和商业游戏 - 固件缺失或版本过新 - 某些游戏可能无法正常运行。Eden需要19.0.1或更低版本的固件。 Qlaunch diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 5c4494c9aa..dcda2d8260 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -350,13 +350,7 @@ 取消 安裝 Amiibo 金鑰 需要在遊戲中使用 Amiibo - 無效的金鑰檔案已選取 - 金鑰已成功安裝 - 讀取加密金鑰時發生錯誤 - 驗證您的金鑰檔案是否具有 .keys 副檔名並再試一次。 驗證您的金鑰檔案是否具有 .bin 副檔名並再試一次。 - 無效的加密金鑰 - 選取的檔案不正確或已損毀,請重新傾印您的金鑰。 GPU 驅動程式管理員 安裝 GPU 驅動程式 安裝替代驅動程式以取得潛在的更佳效能或準確度 @@ -427,8 +421,6 @@ 检查所有安装的内容是否有损坏 密钥缺失 无法解密固件和商业游戏 - 韌體缺失或版本過新 - 某些遊戲可能無法正常運作。Eden需要19.0.1或更低版本的韌體。 Qlaunch diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index d037d2680a..855fd6e769 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -496,4 +496,18 @@ 3 + + "" + @string/error_firmware_missing + @string/error_firmware_corrupted + @string/error_firmware_too_new + + + + "" + "" + @string/error_keys_copy_failed + @string/error_keys_invalid_filename + @string/error_keys_failed_init + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 602be31027..e611e66c1f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -304,14 +304,8 @@ Cancel Install Amiibo keys Required to use Amiibo in game - Invalid keys file selected - Keys successfully installed - Error reading encryption keys - Verify your keys file has a .keys extension and try again. Verify your keys file has a .bin extension and try again. - Invalid encryption keys https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys - The selected file is incorrect or corrupt. Please redump your keys. GPU driver fetcher GPU driver manager Install GPU driver @@ -390,10 +384,18 @@ Firmware and retail games cannot be decrypted https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys - Firmware is missing or too new - Some games may not function properly. Eden requires firmware 19.0.1 or below. + Firmware Invalid + Firmware is required to run certain games and use the Home Menu. Eden only works with firmware 19.0.1 and earlier. + Firmware reported as present, but was unable to be read. Check for decryption keys and redump firmware if necessary. + Firmware is too new or could not be read. Eden only works with firmware 19.0.1 and earlier. https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware + Failed to Install Keys + Keys successfully installed + One or more keys failed to copy. + Verify your keys file has a .keys extension and try again. + Keys failed to initialize. Check that your dumping tools are up to date and re-dump keys. + Qlaunch Launch applications from the system home screen @@ -754,13 +756,16 @@ Your ROM is encrypted - game cartidges or installed titles.]]> + game cartridges or installed titles.]]> prod.keys file is installed so that games can be decrypted.]]> An error occurred initializing the video core This is usually caused by an incompatible GPU driver. Installing a custom GPU driver may resolve this problem. Unable to load ROM ROM file does not exist + Game Requires Firmware + dump and install firmware, or press "OK" to launch anyways.]]> + Exit emulation Done diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 2625e55c35..e0edd006a5 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -15,7 +15,7 @@ #include -static JavaVM* s_java_vm; +static JavaVM *s_java_vm; static jclass s_native_library_class; static jclass s_disk_cache_progress_class; static jclass s_load_callback_stage_class; @@ -26,6 +26,9 @@ static jmethodID s_disk_cache_load_progress; static jmethodID s_on_emulation_started; static jmethodID s_on_emulation_stopped; static jmethodID s_on_program_changed; +static jmethodID s_copy_to_storage; +static jmethodID s_file_exists; +static jmethodID s_file_extension; static jclass s_game_class; static jmethodID s_game_constructor; @@ -102,513 +105,534 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6; namespace Common::Android { -JNIEnv* GetEnvForThread() { - thread_local static struct OwnedEnv { - OwnedEnv() { - status = s_java_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (status == JNI_EDETACHED) - s_java_vm->AttachCurrentThread(&env, nullptr); - } - - ~OwnedEnv() { - if (status == JNI_EDETACHED) - s_java_vm->DetachCurrentThread(); - } - - int status; - JNIEnv* env = nullptr; - } owned; - return owned.env; -} - -jclass GetNativeLibraryClass() { - return s_native_library_class; -} - -jclass GetDiskCacheProgressClass() { - return s_disk_cache_progress_class; -} - -jclass GetDiskCacheLoadCallbackStageClass() { - return s_load_callback_stage_class; -} - -jclass GetGameDirClass() { - return s_game_dir_class; -} - -jmethodID GetGameDirConstructor() { - return s_game_dir_constructor; -} - -jmethodID GetExitEmulationActivity() { - return s_exit_emulation_activity; -} - -jmethodID GetDiskCacheLoadProgress() { - return s_disk_cache_load_progress; -} - -jmethodID GetOnEmulationStarted() { - return s_on_emulation_started; -} - -jmethodID GetOnEmulationStopped() { - return s_on_emulation_stopped; -} - -jmethodID GetOnProgramChanged() { - return s_on_program_changed; -} - -jclass GetGameClass() { - return s_game_class; -} - -jmethodID GetGameConstructor() { - return s_game_constructor; -} - -jfieldID GetGameTitleField() { - return s_game_title_field; -} - -jfieldID GetGamePathField() { - return s_game_path_field; -} - -jfieldID GetGameProgramIdField() { - return s_game_program_id_field; -} - -jfieldID GetGameDeveloperField() { - return s_game_developer_field; -} - -jfieldID GetGameVersionField() { - return s_game_version_field; -} - -jfieldID GetGameIsHomebrewField() { - return s_game_is_homebrew_field; -} - -jclass GetStringClass() { - return s_string_class; -} - -jclass GetPairClass() { - return s_pair_class; -} - -jmethodID GetPairConstructor() { - return s_pair_constructor; -} - -jfieldID GetPairFirstField() { - return s_pair_first_field; -} - -jfieldID GetPairSecondField() { - return s_pair_second_field; -} - -jclass GetOverlayControlDataClass() { - return s_overlay_control_data_class; -} - -jmethodID GetOverlayControlDataConstructor() { - return s_overlay_control_data_constructor; -} - -jfieldID GetOverlayControlDataIdField() { - return s_overlay_control_data_id_field; -} - -jfieldID GetOverlayControlDataEnabledField() { - return s_overlay_control_data_enabled_field; -} - -jfieldID GetOverlayControlDataLandscapePositionField() { - return s_overlay_control_data_landscape_position_field; -} - -jfieldID GetOverlayControlDataPortraitPositionField() { - return s_overlay_control_data_portrait_position_field; -} - -jfieldID GetOverlayControlDataFoldablePositionField() { - return s_overlay_control_data_foldable_position_field; -} - -jclass GetPatchClass() { - return s_patch_class; -} - -jmethodID GetPatchConstructor() { - return s_patch_constructor; -} - -jfieldID GetPatchEnabledField() { - return s_patch_enabled_field; -} - -jfieldID GetPatchNameField() { - return s_patch_name_field; -} - -jfieldID GetPatchVersionField() { - return s_patch_version_field; -} - -jfieldID GetPatchTypeField() { - return s_patch_type_field; -} - -jfieldID GetPatchProgramIdField() { - return s_patch_program_id_field; -} - -jfieldID GetPatchTitleIdField() { - return s_patch_title_id_field; -} - -jclass GetDoubleClass() { - return s_double_class; -} - -jmethodID GetDoubleConstructor() { - return s_double_constructor; -} - -jfieldID GetDoubleValueField() { - return s_double_value_field; -} - -jclass GetIntegerClass() { - return s_integer_class; -} - -jmethodID GetIntegerConstructor() { - return s_integer_constructor; -} - -jfieldID GetIntegerValueField() { - return s_integer_value_field; -} - -jclass GetBooleanClass() { - return s_boolean_class; -} - -jmethodID GetBooleanConstructor() { - return s_boolean_constructor; -} - -jfieldID GetBooleanValueField() { - return s_boolean_value_field; -} - -jclass GetPlayerInputClass() { - return s_player_input_class; -} - -jmethodID GetPlayerInputConstructor() { - return s_player_input_constructor; -} - -jfieldID GetPlayerInputConnectedField() { - return s_player_input_connected_field; -} - -jfieldID GetPlayerInputButtonsField() { - return s_player_input_buttons_field; -} - -jfieldID GetPlayerInputAnalogsField() { - return s_player_input_analogs_field; -} - -jfieldID GetPlayerInputMotionsField() { - return s_player_input_motions_field; -} - -jfieldID GetPlayerInputVibrationEnabledField() { - return s_player_input_vibration_enabled_field; -} - -jfieldID GetPlayerInputVibrationStrengthField() { - return s_player_input_vibration_strength_field; -} - -jfieldID GetPlayerInputBodyColorLeftField() { - return s_player_input_body_color_left_field; -} - -jfieldID GetPlayerInputBodyColorRightField() { - return s_player_input_body_color_right_field; -} - -jfieldID GetPlayerInputButtonColorLeftField() { - return s_player_input_button_color_left_field; -} - -jfieldID GetPlayerInputButtonColorRightField() { - return s_player_input_button_color_right_field; -} - -jfieldID GetPlayerInputProfileNameField() { - return s_player_input_profile_name_field; -} - -jfieldID GetPlayerInputUseSystemVibratorField() { - return s_player_input_use_system_vibrator_field; -} - -jclass GetYuzuInputDeviceInterface() { - return s_yuzu_input_device_interface; -} - -jmethodID GetYuzuDeviceGetName() { - return s_yuzu_input_device_get_name; -} - -jmethodID GetYuzuDeviceGetGUID() { - return s_yuzu_input_device_get_guid; -} - -jmethodID GetYuzuDeviceGetPort() { - return s_yuzu_input_device_get_port; -} - -jmethodID GetYuzuDeviceGetSupportsVibration() { - return s_yuzu_input_device_get_supports_vibration; -} - -jmethodID GetYuzuDeviceVibrate() { - return s_yuzu_input_device_vibrate; -} - -jmethodID GetYuzuDeviceGetAxes() { - return s_yuzu_input_device_get_axes; -} - -jmethodID GetYuzuDeviceHasKeys() { - return s_yuzu_input_device_has_keys; -} - -jmethodID GetAddNetPlayMessage() { - return s_add_netplay_message; -} - -jmethodID ClearChat() { - return s_clear_chat; -} - -#ifdef __cplusplus -extern "C" { -#endif - -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - s_java_vm = vm; - - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) - return JNI_ERR; - - // Initialize Java classes - const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); - s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); - s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( - env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); - s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( - "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); - - const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); - s_game_dir_class = reinterpret_cast(env->NewGlobalRef(game_dir_class)); - s_game_dir_constructor = env->GetMethodID(game_dir_class, "", "(Ljava/lang/String;Z)V"); - env->DeleteLocalRef(game_dir_class); - - // Initialize methods - s_exit_emulation_activity = - env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); - s_disk_cache_load_progress = - env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); - s_on_emulation_started = - env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); - s_on_emulation_stopped = - env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); - s_on_program_changed = - env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); - - const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); - s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); - s_game_constructor = env->GetMethodID(game_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;Ljava/lang/String;Z)V"); - s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); - s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); - s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); - s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); - s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); - s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); - env->DeleteLocalRef(game_class); - - const jclass string_class = env->FindClass("java/lang/String"); - s_string_class = reinterpret_cast(env->NewGlobalRef(string_class)); - env->DeleteLocalRef(string_class); - - const jclass pair_class = env->FindClass("kotlin/Pair"); - s_pair_class = reinterpret_cast(env->NewGlobalRef(pair_class)); - s_pair_constructor = - env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); - s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); - env->DeleteLocalRef(pair_class); - - const jclass overlay_control_data_class = - env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); - s_overlay_control_data_class = - reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); - s_overlay_control_data_constructor = - env->GetMethodID(overlay_control_data_class, "", - "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); - s_overlay_control_data_id_field = - env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); - s_overlay_control_data_enabled_field = - env->GetFieldID(overlay_control_data_class, "enabled", "Z"); - s_overlay_control_data_landscape_position_field = - env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); - s_overlay_control_data_portrait_position_field = - env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); - s_overlay_control_data_foldable_position_field = - env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); - env->DeleteLocalRef(overlay_control_data_class); - - const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); - s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); - s_patch_constructor = env->GetMethodID( - patch_class, "", - "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); - s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); - s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); - s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); - s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); - s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); - s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); - env->DeleteLocalRef(patch_class); - - const jclass double_class = env->FindClass("java/lang/Double"); - s_double_class = reinterpret_cast(env->NewGlobalRef(double_class)); - s_double_constructor = env->GetMethodID(double_class, "", "(D)V"); - s_double_value_field = env->GetFieldID(double_class, "value", "D"); - env->DeleteLocalRef(double_class); - - const jclass int_class = env->FindClass("java/lang/Integer"); - s_integer_class = reinterpret_cast(env->NewGlobalRef(int_class)); - s_integer_constructor = env->GetMethodID(int_class, "", "(I)V"); - s_integer_value_field = env->GetFieldID(int_class, "value", "I"); - env->DeleteLocalRef(int_class); - - const jclass boolean_class = env->FindClass("java/lang/Boolean"); - s_boolean_class = reinterpret_cast(env->NewGlobalRef(boolean_class)); - s_boolean_constructor = env->GetMethodID(boolean_class, "", "(Z)V"); - s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); - env->DeleteLocalRef(boolean_class); - - const jclass player_input_class = - env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput"); - s_player_input_class = reinterpret_cast(env->NewGlobalRef(player_input_class)); - s_player_input_constructor = env->GetMethodID( - player_input_class, "", - "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V"); - s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z"); - s_player_input_buttons_field = - env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;"); - s_player_input_analogs_field = - env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;"); - s_player_input_motions_field = - env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;"); - s_player_input_vibration_enabled_field = - env->GetFieldID(player_input_class, "vibrationEnabled", "Z"); - s_player_input_vibration_strength_field = - env->GetFieldID(player_input_class, "vibrationStrength", "I"); - s_player_input_body_color_left_field = - env->GetFieldID(player_input_class, "bodyColorLeft", "J"); - s_player_input_body_color_right_field = - env->GetFieldID(player_input_class, "bodyColorRight", "J"); - s_player_input_button_color_left_field = - env->GetFieldID(player_input_class, "buttonColorLeft", "J"); - s_player_input_button_color_right_field = - env->GetFieldID(player_input_class, "buttonColorRight", "J"); - s_player_input_profile_name_field = - env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;"); - s_player_input_use_system_vibrator_field = - env->GetFieldID(player_input_class, "useSystemVibrator", "Z"); - env->DeleteLocalRef(player_input_class); - - const jclass yuzu_input_device_interface = - env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice"); - s_yuzu_input_device_interface = - reinterpret_cast(env->NewGlobalRef(yuzu_input_device_interface)); - s_yuzu_input_device_get_name = - env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;"); - s_yuzu_input_device_get_guid = - env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;"); - s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", "()I"); - s_yuzu_input_device_get_supports_vibration = - env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z"); - s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", "(F)V"); - s_yuzu_input_device_get_axes = - env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;"); - s_yuzu_input_device_has_keys = - env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z"); - env->DeleteLocalRef(yuzu_input_device_interface); - s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage", - "(ILjava/lang/String;)V"); - s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V"); - - - // Initialize Android Storage - Common::FS::Android::RegisterCallbacks(env, s_native_library_class); - - // Initialize applets - Common::Android::SoftwareKeyboard::InitJNI(env); - - return JNI_VERSION; -} - -void JNI_OnUnload(JavaVM* vm, void* reserved) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { - return; + JNIEnv *GetEnvForThread() { + thread_local static struct OwnedEnv { + OwnedEnv() { + status = s_java_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (status == JNI_EDETACHED) + s_java_vm->AttachCurrentThread(&env, nullptr); + } + + ~OwnedEnv() { + if (status == JNI_EDETACHED) + s_java_vm->DetachCurrentThread(); + } + + int status; + JNIEnv *env = nullptr; + } owned; + return owned.env; } - // UnInitialize Android Storage - Common::FS::Android::UnRegisterCallbacks(); - env->DeleteGlobalRef(s_native_library_class); - env->DeleteGlobalRef(s_disk_cache_progress_class); - env->DeleteGlobalRef(s_load_callback_stage_class); - env->DeleteGlobalRef(s_game_dir_class); - env->DeleteGlobalRef(s_game_class); - env->DeleteGlobalRef(s_string_class); - env->DeleteGlobalRef(s_pair_class); - env->DeleteGlobalRef(s_overlay_control_data_class); - env->DeleteGlobalRef(s_patch_class); - env->DeleteGlobalRef(s_double_class); - env->DeleteGlobalRef(s_integer_class); - env->DeleteGlobalRef(s_boolean_class); - env->DeleteGlobalRef(s_player_input_class); - env->DeleteGlobalRef(s_yuzu_input_device_interface); + jclass GetNativeLibraryClass() { + return s_native_library_class; + } - // UnInitialize applets - SoftwareKeyboard::CleanupJNI(env); + jclass GetDiskCacheProgressClass() { + return s_disk_cache_progress_class; + } - AndroidMultiplayer::NetworkShutdown(); -} + jclass GetDiskCacheLoadCallbackStageClass() { + return s_load_callback_stage_class; + } + + jclass GetGameDirClass() { + return s_game_dir_class; + } + + jmethodID GetGameDirConstructor() { + return s_game_dir_constructor; + } + + jmethodID GetExitEmulationActivity() { + return s_exit_emulation_activity; + } + + jmethodID GetDiskCacheLoadProgress() { + return s_disk_cache_load_progress; + } + + jmethodID GetCopyToStorage() { + return s_copy_to_storage; + } + + jmethodID GetFileExists() { + return s_file_exists; + } + + jmethodID GetFileExtension() { + return s_file_extension; + } + + jmethodID GetOnEmulationStarted() { + return s_on_emulation_started; + } + + jmethodID GetOnEmulationStopped() { + return s_on_emulation_stopped; + } + + jmethodID GetOnProgramChanged() { + return s_on_program_changed; + } + + jclass GetGameClass() { + return s_game_class; + } + + jmethodID GetGameConstructor() { + return s_game_constructor; + } + + jfieldID GetGameTitleField() { + return s_game_title_field; + } + + jfieldID GetGamePathField() { + return s_game_path_field; + } + + jfieldID GetGameProgramIdField() { + return s_game_program_id_field; + } + + jfieldID GetGameDeveloperField() { + return s_game_developer_field; + } + + jfieldID GetGameVersionField() { + return s_game_version_field; + } + + jfieldID GetGameIsHomebrewField() { + return s_game_is_homebrew_field; + } + + jclass GetStringClass() { + return s_string_class; + } + + jclass GetPairClass() { + return s_pair_class; + } + + jmethodID GetPairConstructor() { + return s_pair_constructor; + } + + jfieldID GetPairFirstField() { + return s_pair_first_field; + } + + jfieldID GetPairSecondField() { + return s_pair_second_field; + } + + jclass GetOverlayControlDataClass() { + return s_overlay_control_data_class; + } + + jmethodID GetOverlayControlDataConstructor() { + return s_overlay_control_data_constructor; + } + + jfieldID GetOverlayControlDataIdField() { + return s_overlay_control_data_id_field; + } + + jfieldID GetOverlayControlDataEnabledField() { + return s_overlay_control_data_enabled_field; + } + + jfieldID GetOverlayControlDataLandscapePositionField() { + return s_overlay_control_data_landscape_position_field; + } + + jfieldID GetOverlayControlDataPortraitPositionField() { + return s_overlay_control_data_portrait_position_field; + } + + jfieldID GetOverlayControlDataFoldablePositionField() { + return s_overlay_control_data_foldable_position_field; + } + + jclass GetPatchClass() { + return s_patch_class; + } + + jmethodID GetPatchConstructor() { + return s_patch_constructor; + } + + jfieldID GetPatchEnabledField() { + return s_patch_enabled_field; + } + + jfieldID GetPatchNameField() { + return s_patch_name_field; + } + + jfieldID GetPatchVersionField() { + return s_patch_version_field; + } + + jfieldID GetPatchTypeField() { + return s_patch_type_field; + } + + jfieldID GetPatchProgramIdField() { + return s_patch_program_id_field; + } + + jfieldID GetPatchTitleIdField() { + return s_patch_title_id_field; + } + + jclass GetDoubleClass() { + return s_double_class; + } + + jmethodID GetDoubleConstructor() { + return s_double_constructor; + } + + jfieldID GetDoubleValueField() { + return s_double_value_field; + } + + jclass GetIntegerClass() { + return s_integer_class; + } + + jmethodID GetIntegerConstructor() { + return s_integer_constructor; + } + + jfieldID GetIntegerValueField() { + return s_integer_value_field; + } + + jclass GetBooleanClass() { + return s_boolean_class; + } + + jmethodID GetBooleanConstructor() { + return s_boolean_constructor; + } + + jfieldID GetBooleanValueField() { + return s_boolean_value_field; + } + + jclass GetPlayerInputClass() { + return s_player_input_class; + } + + jmethodID GetPlayerInputConstructor() { + return s_player_input_constructor; + } + + jfieldID GetPlayerInputConnectedField() { + return s_player_input_connected_field; + } + + jfieldID GetPlayerInputButtonsField() { + return s_player_input_buttons_field; + } + + jfieldID GetPlayerInputAnalogsField() { + return s_player_input_analogs_field; + } + + jfieldID GetPlayerInputMotionsField() { + return s_player_input_motions_field; + } + + jfieldID GetPlayerInputVibrationEnabledField() { + return s_player_input_vibration_enabled_field; + } + + jfieldID GetPlayerInputVibrationStrengthField() { + return s_player_input_vibration_strength_field; + } + + jfieldID GetPlayerInputBodyColorLeftField() { + return s_player_input_body_color_left_field; + } + + jfieldID GetPlayerInputBodyColorRightField() { + return s_player_input_body_color_right_field; + } + + jfieldID GetPlayerInputButtonColorLeftField() { + return s_player_input_button_color_left_field; + } + + jfieldID GetPlayerInputButtonColorRightField() { + return s_player_input_button_color_right_field; + } + + jfieldID GetPlayerInputProfileNameField() { + return s_player_input_profile_name_field; + } + + jfieldID GetPlayerInputUseSystemVibratorField() { + return s_player_input_use_system_vibrator_field; + } + + jclass GetYuzuInputDeviceInterface() { + return s_yuzu_input_device_interface; + } + + jmethodID GetYuzuDeviceGetName() { + return s_yuzu_input_device_get_name; + } + + jmethodID GetYuzuDeviceGetGUID() { + return s_yuzu_input_device_get_guid; + } + + jmethodID GetYuzuDeviceGetPort() { + return s_yuzu_input_device_get_port; + } + + jmethodID GetYuzuDeviceGetSupportsVibration() { + return s_yuzu_input_device_get_supports_vibration; + } + + jmethodID GetYuzuDeviceVibrate() { + return s_yuzu_input_device_vibrate; + } + + jmethodID GetYuzuDeviceGetAxes() { + return s_yuzu_input_device_get_axes; + } + + jmethodID GetYuzuDeviceHasKeys() { + return s_yuzu_input_device_has_keys; + } + + jmethodID GetAddNetPlayMessage() { + return s_add_netplay_message; + } + + jmethodID ClearChat() { + return s_clear_chat; + } #ifdef __cplusplus -} + extern "C" { +#endif + + jint JNI_OnLoad(JavaVM *vm, void *reserved) { + s_java_vm = vm; + + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) + return JNI_ERR; + + // Initialize Java classes + const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); + s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); + s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( + env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); + s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( + "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); + + const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); + s_game_dir_class = reinterpret_cast(env->NewGlobalRef(game_dir_class)); + s_game_dir_constructor = env->GetMethodID(game_dir_class, "", + "(Ljava/lang/String;Z)V"); + env->DeleteLocalRef(game_dir_class); + + // Initialize methods + s_exit_emulation_activity = + env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); + s_disk_cache_load_progress = + env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); + s_copy_to_storage = env->GetStaticMethodID(s_native_library_class, "copyFileToStorage", + "(Ljava/lang/String;Ljava/lang/String;)Z"); + s_file_exists = env->GetStaticMethodID(s_native_library_class, "exists", + "(Ljava/lang/String;)Z"); + s_file_extension = env->GetStaticMethodID(s_native_library_class, "getFileExtension", + "(Ljava/lang/String;)Ljava/lang/String;"); + s_on_emulation_started = + env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); + s_on_emulation_stopped = + env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); + s_on_program_changed = + env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); + + const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); + s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); + s_game_constructor = env->GetMethodID(game_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;Z)V"); + s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); + s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); + s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); + s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); + s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); + s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); + env->DeleteLocalRef(game_class); + + const jclass string_class = env->FindClass("java/lang/String"); + s_string_class = reinterpret_cast(env->NewGlobalRef(string_class)); + env->DeleteLocalRef(string_class); + + const jclass pair_class = env->FindClass("kotlin/Pair"); + s_pair_class = reinterpret_cast(env->NewGlobalRef(pair_class)); + s_pair_constructor = + env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); + s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); + env->DeleteLocalRef(pair_class); + + const jclass overlay_control_data_class = + env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); + s_overlay_control_data_class = + reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); + s_overlay_control_data_constructor = + env->GetMethodID(overlay_control_data_class, "", + "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); + s_overlay_control_data_id_field = + env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); + s_overlay_control_data_enabled_field = + env->GetFieldID(overlay_control_data_class, "enabled", "Z"); + s_overlay_control_data_landscape_position_field = + env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); + s_overlay_control_data_portrait_position_field = + env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); + s_overlay_control_data_foldable_position_field = + env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); + env->DeleteLocalRef(overlay_control_data_class); + + const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); + s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); + s_patch_constructor = env->GetMethodID( + patch_class, "", + "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); + s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); + s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); + s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); + s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); + s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); + s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); + env->DeleteLocalRef(patch_class); + + const jclass double_class = env->FindClass("java/lang/Double"); + s_double_class = reinterpret_cast(env->NewGlobalRef(double_class)); + s_double_constructor = env->GetMethodID(double_class, "", "(D)V"); + s_double_value_field = env->GetFieldID(double_class, "value", "D"); + env->DeleteLocalRef(double_class); + + const jclass int_class = env->FindClass("java/lang/Integer"); + s_integer_class = reinterpret_cast(env->NewGlobalRef(int_class)); + s_integer_constructor = env->GetMethodID(int_class, "", "(I)V"); + s_integer_value_field = env->GetFieldID(int_class, "value", "I"); + env->DeleteLocalRef(int_class); + + const jclass boolean_class = env->FindClass("java/lang/Boolean"); + s_boolean_class = reinterpret_cast(env->NewGlobalRef(boolean_class)); + s_boolean_constructor = env->GetMethodID(boolean_class, "", "(Z)V"); + s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); + env->DeleteLocalRef(boolean_class); + + const jclass player_input_class = + env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput"); + s_player_input_class = reinterpret_cast(env->NewGlobalRef(player_input_class)); + s_player_input_constructor = env->GetMethodID( + player_input_class, "", + "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V"); + s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z"); + s_player_input_buttons_field = + env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;"); + s_player_input_analogs_field = + env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;"); + s_player_input_motions_field = + env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;"); + s_player_input_vibration_enabled_field = + env->GetFieldID(player_input_class, "vibrationEnabled", "Z"); + s_player_input_vibration_strength_field = + env->GetFieldID(player_input_class, "vibrationStrength", "I"); + s_player_input_body_color_left_field = + env->GetFieldID(player_input_class, "bodyColorLeft", "J"); + s_player_input_body_color_right_field = + env->GetFieldID(player_input_class, "bodyColorRight", "J"); + s_player_input_button_color_left_field = + env->GetFieldID(player_input_class, "buttonColorLeft", "J"); + s_player_input_button_color_right_field = + env->GetFieldID(player_input_class, "buttonColorRight", "J"); + s_player_input_profile_name_field = + env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;"); + s_player_input_use_system_vibrator_field = + env->GetFieldID(player_input_class, "useSystemVibrator", "Z"); + env->DeleteLocalRef(player_input_class); + + const jclass yuzu_input_device_interface = + env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice"); + s_yuzu_input_device_interface = + reinterpret_cast(env->NewGlobalRef(yuzu_input_device_interface)); + s_yuzu_input_device_get_name = + env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;"); + s_yuzu_input_device_get_guid = + env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;"); + s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", + "()I"); + s_yuzu_input_device_get_supports_vibration = + env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z"); + s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", + "(F)V"); + s_yuzu_input_device_get_axes = + env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;"); + s_yuzu_input_device_has_keys = + env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z"); + env->DeleteLocalRef(yuzu_input_device_interface); + s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage", + "(ILjava/lang/String;)V"); + s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V"); + + + // Initialize Android Storage + Common::FS::Android::RegisterCallbacks(env, s_native_library_class); + + // Initialize applets + Common::Android::SoftwareKeyboard::InitJNI(env); + + return JNI_VERSION; + } + + void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { + return; + } + + // UnInitialize Android Storage + Common::FS::Android::UnRegisterCallbacks(); + env->DeleteGlobalRef(s_native_library_class); + env->DeleteGlobalRef(s_disk_cache_progress_class); + env->DeleteGlobalRef(s_load_callback_stage_class); + env->DeleteGlobalRef(s_game_dir_class); + env->DeleteGlobalRef(s_game_class); + env->DeleteGlobalRef(s_string_class); + env->DeleteGlobalRef(s_pair_class); + env->DeleteGlobalRef(s_overlay_control_data_class); + env->DeleteGlobalRef(s_patch_class); + env->DeleteGlobalRef(s_double_class); + env->DeleteGlobalRef(s_integer_class); + env->DeleteGlobalRef(s_boolean_class); + env->DeleteGlobalRef(s_player_input_class); + env->DeleteGlobalRef(s_yuzu_input_device_interface); + + // UnInitialize applets + SoftwareKeyboard::CleanupJNI(env); + + AndroidMultiplayer::NetworkShutdown(); + } + +#ifdef __cplusplus + } #endif } // namespace Common::Android diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index cbfbf36be8..c56ffcf5c6 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -39,6 +39,9 @@ jclass GetDiskCacheLoadCallbackStageClass(); jclass GetGameDirClass(); jmethodID GetGameDirConstructor(); jmethodID GetDiskCacheLoadProgress(); +jmethodID GetCopyToStorage(); +jmethodID GetFileExists(); +jmethodID GetFileExtension(); jmethodID GetExitEmulationActivity(); jmethodID GetOnEmulationStarted(); diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt index 94d8cc4c3b..70e142bb0c 100644 --- a/src/frontend_common/CMakeLists.txt +++ b/src/frontend_common/CMakeLists.txt @@ -5,6 +5,8 @@ add_library(frontend_common STATIC config.cpp config.h content_manager.h + firmware_manager.h + firmware_manager.cpp ) create_target_directory_groups(frontend_common) diff --git a/src/frontend_common/firmware_manager.cpp b/src/frontend_common/firmware_manager.cpp new file mode 100644 index 0000000000..f1022579be --- /dev/null +++ b/src/frontend_common/firmware_manager.cpp @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "firmware_manager.h" +#include +#include + +#include "common/fs/fs.h" +#include "common/fs/path_util.h" + +#include "common/logging/backend.h" + +#include "core/crypto/key_manager.h" +#include "frontend_common/content_manager.h" + +#ifdef ANDROID +#include +#include +#include +#endif + +FirmwareManager::KeyInstallResult +FirmwareManager::InstallKeys(std::string location, std::string extension) { + LOG_INFO(Frontend, "Installing key files from {}", location); + + const auto keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir); + +#ifdef ANDROID + JNIEnv *env = Common::Android::GetEnvForThread(); + + jstring jsrc = Common::Android::ToJString(env, location); + + jclass native = Common::Android::GetNativeLibraryClass(); + jmethodID getExtension = Common::Android::GetFileExtension(); + + jstring jext = static_cast(env->CallStaticObjectMethod( + native, + getExtension, + jsrc + )); + + std::string ext = Common::Android::GetJString(env, jext); + + if (ext != extension) { + return ErrorWrongFilename; + } + + jmethodID copyToStorage = Common::Android::GetCopyToStorage(); + jstring jdest = Common::Android::ToJString(env, keys_dir.string()); + + jboolean copyResult = env->CallStaticBooleanMethod( + native, + copyToStorage, + jsrc, + jdest + ); + + if (!copyResult) { + return ErrorFailedCopy; + } +#else + if (!location.ends_with(extension)) { + return ErrorWrongFilename; + } + + bool prod_keys_found = false; + + const std::filesystem::path prod_key_path = location; + const std::filesystem::path key_source_path = prod_key_path.parent_path(); + + if (!Common::FS::IsDir(key_source_path)) { + return InvalidDir; + } + + std::vector source_key_files; + + if (Common::FS::Exists(prod_key_path)) { + prod_keys_found = true; + source_key_files.emplace_back(prod_key_path); + } + + if (Common::FS::Exists(key_source_path / "title.keys")) { + source_key_files.emplace_back(key_source_path / "title.keys"); + } + + if (Common::FS::Exists(key_source_path / "key_retail.bin")) { + source_key_files.emplace_back(key_source_path / "key_retail.bin"); + } + + if (source_key_files.empty() || !prod_keys_found) { + return ErrorWrongFilename; + } + + for (const auto &key_file : source_key_files) { + std::filesystem::path destination_key_file = keys_dir / key_file.filename(); + if (!std::filesystem::copy_file(key_file, + destination_key_file, + std::filesystem::copy_options::overwrite_existing)) { + LOG_ERROR(Frontend, + "Failed to copy file {} to {}", + key_file.string(), + destination_key_file.string()); + return ErrorFailedCopy; + } + } +#endif + + // Reinitialize the key manager + Core::Crypto::KeyManager::Instance().ReloadKeys(); + + if (ContentManager::AreKeysPresent()) { + return Success; + } + + // Let the frontend handle everything else + return ErrorFailedInit; +} + +FirmwareManager::FirmwareCheckResult FirmwareManager::VerifyFirmware(Core::System &system) { + if (!CheckFirmwarePresence(system)) { + return ErrorFirmwareMissing; + } else { + const auto pair = GetFirmwareVersion(system); + const auto firmware_data = pair.first; + const auto result = pair.second; + + if (result.IsError()) { + LOG_INFO(Frontend, "Unable to read firmware"); + return ErrorFirmwareCorrupted; + } + + // TODO: update this whenever newer firmware is properly supported + if (firmware_data.major > 19) { + return ErrorFirmwareTooNew; + } + } + + return FirmwareGood; +} diff --git a/src/frontend_common/firmware_manager.h b/src/frontend_common/firmware_manager.h new file mode 100644 index 0000000000..20f3b41478 --- /dev/null +++ b/src/frontend_common/firmware_manager.h @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef FIRMWARE_MANAGER_H +#define FIRMWARE_MANAGER_H + +#include "common/common_types.h" +#include "core/core.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/filesystem/filesystem.h" +#include +#include +#include +#include + +#include "core/hle/service/set/settings_types.h" +#include "core/hle/service/set/system_settings_server.h" +#include "core/hle/result.h" + +namespace FirmwareManager { + +static constexpr std::array KEY_INSTALL_RESULT_STRINGS = { + "Decryption Keys were successfully installed", + "Unable to read key directory, aborting", + "One or more keys failed to copy.", + "Verify your keys file has a .keys extension and try again.", + "Decryption Keys failed to initialize. Check that your dumping tools are up to date and " + "re-dump keys.", +}; + +static constexpr std::array FIRMWARE_REQUIRED_GAMES = { + 0x0100152000022000ULL, // MK8DX +}; + +enum KeyInstallResult { + Success, + InvalidDir, + ErrorFailedCopy, + ErrorWrongFilename, + ErrorFailedInit, +}; + +/** + * @brief Installs any arbitrary set of keys for the emulator. + * @param location Where the keys are located. + * @param expected_extension What extension the file should have. + * @return A result code for the operation. + */ +KeyInstallResult InstallKeys(std::string location, std::string expected_extension); + +/** + * \brief Get a string representation of a result from InstallKeys. + * \param result The result code. + * \return A string representation of the passed result code. + */ +inline constexpr const char *GetKeyInstallResultString(KeyInstallResult result) +{ + return KEY_INSTALL_RESULT_STRINGS.at(static_cast(result)); +} + +/** + * \brief Check if the specified program requires firmware to run properly. + * It is the responsibility of the frontend to properly expose this to the user. + * \param program_id The program ID to check. + * \return Whether or not the program requires firmware to run properly. + */ +inline constexpr bool GameRequiresFirmware(u64 program_id) +{ + return std::find(FIRMWARE_REQUIRED_GAMES.begin(), FIRMWARE_REQUIRED_GAMES.end(), program_id) + != FIRMWARE_REQUIRED_GAMES.end(); +} + + +enum FirmwareCheckResult { + FirmwareGood, + ErrorFirmwareMissing, + ErrorFirmwareCorrupted, + ErrorFirmwareTooNew, +}; + +static constexpr std::array FIRMWARE_CHECK_STRINGS = { + "", + "Firmware missing. Firmware is required to run certain games and use the Home Menu. " + "Eden only works with firmware 19.0.1 and earlier.", + "Firmware reported as present, but was unable to be read. Check for decryption keys and " + "redump firmware if necessary.", + "Firmware is too new or could not be read. Eden only works with firmware 19.0.1 and earlier.", +}; + +/** + * \brief Checks for installed firmware within the system. + * \param system The system to check for firmware. + * \return Whether or not the system has installed firmware. + */ +inline bool CheckFirmwarePresence(Core::System &system) +{ + constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit); + + auto bis_system = system.GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + return false; + } + + auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); + + if (!mii_applet_nca) { + return false; + } + + return true; +} + +/** + * \brief Verifies if firmware is properly installed and is in the correct version range. + * \param system The system to check firmware on. + * \return A result code defining the status of the system's firmware. + */ +FirmwareCheckResult VerifyFirmware(Core::System &system); + +/** + * \brief Get a string representation of a result from CheckFirmwareVersion. + * \param result The result code. + * \return A string representation of the passed result code. + */ +inline constexpr const char *GetFirmwareCheckString(FirmwareCheckResult result) +{ + return FIRMWARE_CHECK_STRINGS.at(static_cast(result)); +} + +/** + * @brief Get the currently installed firmware version. + * @param system The system to check firmware on. + * @return A pair of the firmware version format and result code. + */ +inline std::pair GetFirmwareVersion(Core::System &system) +{ + Service::Set::FirmwareVersionFormat firmware_data{}; + const auto result + = Service::Set::GetFirmwareVersionImpl(firmware_data, + system, + Service::Set::GetFirmwareVersionType::Version2); + + return {firmware_data, result}; +} +} + +#endif // FIRMWARE_MANAGER_H diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4ff59291e5..081f08cf52 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -10,6 +10,7 @@ #include "core/hle/service/am/applet_manager.h" #include "core/loader/nca.h" #include "core/tools/renderdoc.h" +#include "frontend_common/firmware_manager.h" #ifdef __APPLE__ #include // for chdir @@ -211,7 +212,7 @@ enum class CalloutFlag : uint32_t { * Some games perform worse or straight-up don't work with updates, * so this tracks which games are bad in this regard. */ -static const QList bad_update_games{ +constexpr std::array bad_update_games{ 0x0100F2C0115B6000 // Tears of the Kingdom }; @@ -1889,46 +1890,61 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa QSettings settings; QStringList currentIgnored = settings.value("ignoredBadUpdates", {}).toStringList(); - for (const u64 id : bad_update_games) { - const bool ignored = currentIgnored.contains(QString::number(id)); + if (std::find(bad_update_games.begin(), bad_update_games.end(), params.program_id) != bad_update_games.end() + && !currentIgnored.contains(QString::number(params.program_id))) { + QMessageBox *msg = new QMessageBox(this); + msg->setWindowTitle(tr("Game Updates Warning")); + msg->setIcon(QMessageBox::Warning); + msg->setText(tr("The game you are trying to launch is known to have performance or booting " + "issues when updates are applied. Please try increasing the memory layout to " + "6GB or 8GB if any issues occur.

Press \"OK\" to continue launching, or " + "\"Cancel\" to cancel the launch.")); - if (params.program_id == id && !ignored) { - QMessageBox *msg = new QMessageBox(this); - msg->setWindowTitle(tr("Game Updates Warning")); - msg->setIcon(QMessageBox::Warning); - msg->setText(tr("The game you are trying to launch is known to have performance or booting " - "issues when updates are applied. Please try increasing the memory layout to " - "6GB or 8GB if any issues occur.

Press \"OK\" to continue launching, or " - "\"Cancel\" to cancel the launch.")); + msg->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - // TODO: TMP: Recommends more memory for TotK. + QCheckBox *dontShowAgain = new QCheckBox(msg); + dontShowAgain->setText(tr("Don't show again for this game")); + msg->setCheckBox(dontShowAgain); - msg->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + int result = msg->exec(); - QCheckBox *dontShowAgain = new QCheckBox(msg); - dontShowAgain->setText(tr("Don't show again for this game")); - msg->setCheckBox(dontShowAgain); + // wtf + QMessageBox::ButtonRole role = msg->buttonRole(msg->button((QMessageBox::StandardButton) result)); - int result = msg->exec(); - - // wtf - QMessageBox::ButtonRole role = msg->buttonRole(msg->button((QMessageBox::StandardButton) result)); - - switch (role) { - - case QMessageBox::RejectRole: - return false; - - case QMessageBox::AcceptRole: - default: - if (dontShowAgain->isChecked()) { - currentIgnored << QString::number(params.program_id); - - settings.setValue("ignoredBadUpdates", currentIgnored); - settings.sync(); - } - break; + switch (role) { + case QMessageBox::RejectRole: + return false; + case QMessageBox::AcceptRole: + default: + if (dontShowAgain->isChecked()) { + currentIgnored << QString::number(params.program_id); + settings.setValue("ignoredBadUpdates", currentIgnored); + settings.sync(); } + break; + } + } + + if (FirmwareManager::GameRequiresFirmware(params.program_id) && !FirmwareManager::CheckFirmwarePresence(*system)) { + QMessageBox *msg = new QMessageBox(this); + msg->setWindowTitle(tr("Game Requires Firmware")); + msg->setIcon(QMessageBox::Warning); + msg->setText(tr("The game you are trying to launch requires firmware to boot or to get past the " + "opening menu. Please " + "dump and install firmware, or press \"OK\" to launch anyways.")); + + msg->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + + int exec_result = msg->exec(); + + QMessageBox::ButtonRole role = msg->buttonRole(msg->button((QMessageBox::StandardButton) exec_result)); + + switch (role) { + case QMessageBox::RejectRole: + return false; + case QMessageBox::AcceptRole: + default: + break; } } @@ -1945,7 +1961,7 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa UISettings::values.callout_flags = UISettings::values.callout_flags.GetValue() | static_cast(CalloutFlag::DRDDeprecation); QMessageBox::warning( - this, tr("Warning Outdated Game Format"), + this, tr("Warning: Outdated Game Format"), tr("You are using the deconstructed ROM directory format for this game, which is an " "outdated format that has been superseded by others such as NCA, NAX, XCI, or " "NSP. Deconstructed ROM directories lack icons, metadata, and update " @@ -1968,7 +1984,7 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa "This is usually caused by outdated GPU drivers, including integrated ones. " "Please see the log for more details. " "For more information on accessing the log, please see the following page: " - "" + "" "How to Upload the Log File. ")); break; default: @@ -4322,8 +4338,8 @@ void GMainWindow::OnInstallFirmware() { progress.close(); QMessageBox::warning( this, tr("Firmware install failed"), - tr("Firmware installation cancelled, firmware may be in bad state, " - "restart eden or re-install firmware.")); + tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. " + "Restart Eden or re-install firmware.")); return; } } @@ -4368,72 +4384,27 @@ void GMainWindow::OnInstallDecryptionKeys() { } const QString key_source_location = QFileDialog::getOpenFileName( - this, tr("Select Dumped Keys Location"), {}, QStringLiteral("prod.keys (prod.keys)"), {}, + this, tr("Select Dumped Keys Location"), {}, QStringLiteral("Decryption Keys (*.keys)"), {}, QFileDialog::ReadOnly); if (key_source_location.isEmpty()) { return; } - // Verify that it contains prod.keys, title.keys and optionally, key_retail.bin - LOG_INFO(Frontend, "Installing key files from {}", key_source_location.toStdString()); + FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys"); - const std::filesystem::path prod_key_path = key_source_location.toStdString(); - const std::filesystem::path key_source_path = prod_key_path.parent_path(); - if (!Common::FS::IsDir(key_source_path)) { - return; - } - - bool prod_keys_found = false; - std::vector source_key_files; - - if (Common::FS::Exists(prod_key_path)) { - prod_keys_found = true; - source_key_files.emplace_back(prod_key_path); - } - - if (Common::FS::Exists(key_source_path / "title.keys")) { - source_key_files.emplace_back(key_source_path / "title.keys"); - } - - if (Common::FS::Exists(key_source_path / "key_retail.bin")) { - source_key_files.emplace_back(key_source_path / "key_retail.bin"); - } - - // There should be at least prod.keys. - if (source_key_files.empty() || !prod_keys_found) { - QMessageBox::warning(this, tr("Decryption Keys install failed"), - tr("prod.keys is a required decryption key file.")); - return; - } - - const auto yuzu_keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir); - for (auto key_file : source_key_files) { - std::filesystem::path destination_key_file = yuzu_keys_dir / key_file.filename(); - if (!std::filesystem::copy_file(key_file, destination_key_file, - std::filesystem::copy_options::overwrite_existing)) { - LOG_ERROR(Frontend, "Failed to copy file {} to {}", key_file.string(), - destination_key_file.string()); - QMessageBox::critical(this, tr("Decryption Keys install failed"), - tr("One or more keys failed to copy.")); - return; - } - } - - // Reinitialize the key manager, re-read the vfs (for update/dlc files), - // and re-populate the game list in the UI if the user has already added - // game folders. - Core::Crypto::KeyManager::Instance().ReloadKeys(); system->GetFileSystemController().CreateFactories(*vfs); game_list->PopulateAsync(UISettings::values.game_dirs); - if (ContentManager::AreKeysPresent()) { + switch (result) { + case FirmwareManager::KeyInstallResult::Success: QMessageBox::information(this, tr("Decryption Keys install succeeded"), tr("Decryption Keys were successfully installed")); - } else { + break; + default: QMessageBox::critical( this, tr("Decryption Keys install failed"), - tr("Decryption Keys failed to initialize. Check that your dumping tools are " - "up to date and re-dump keys.")); + tr(FirmwareManager::GetKeyInstallResultString(result))); + break; } OnCheckFirmwareDecryption(); @@ -5103,52 +5074,27 @@ void GMainWindow::OnCheckFirmwareDecryption() { void GMainWindow::OnCheckFirmware() { - if (!CheckFirmwarePresence()) { + auto result = FirmwareManager::VerifyFirmware(*system.get()); + + switch (result) { + case FirmwareManager::FirmwareGood: + break; + default: QMessageBox::warning( - this, tr("Firmware Missing"), - tr("Firmware missing. Firmware is required to run certain games and use the Home Menu.\n" - "Eden only works with firmware 19.0.1 and earlier.")); - } else { - Service::Set::FirmwareVersionFormat firmware_data{}; - const auto result = Service::Set::GetFirmwareVersionImpl( - firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2); - - if (result.IsError()) { - LOG_INFO(Frontend, "Unable to read firmware"); - QMessageBox::warning( - this, tr("Firmware Corrupted"), - tr("Firmware reported as present, but was unable to be read. Check for decryption keys and redump firmware if necessary.")); - return; - } - - if (firmware_data.major > 19) { - QMessageBox::warning( - this, tr("Firmware Too New"), - tr("Firmware is too new. Eden only works with firmware 19.0.1 and earlier.")); - } + this, tr("Firmware Read Error"), + tr(FirmwareManager::GetFirmwareCheckString(result))); + break; } } bool GMainWindow::CheckFirmwarePresence() { - constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit); - - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - return false; - } - - auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); - if (!mii_applet_nca) { - return false; - } - - return true; + return FirmwareManager::CheckFirmwarePresence(*system.get()); } void GMainWindow::SetFirmwareVersion() { - Service::Set::FirmwareVersionFormat firmware_data{}; - const auto result = Service::Set::GetFirmwareVersionImpl( - firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2); + const auto pair = FirmwareManager::GetFirmwareVersion(*system.get()); + const auto firmware_data = pair.first; + const auto result = pair.second; if (result.IsError() || !CheckFirmwarePresence()) { LOG_INFO(Frontend, "Installed firmware: No firmware available");