From 5435f0be5e4d81da5140cea79904252403f108c2 Mon Sep 17 00:00:00 2001 From: PabloG02 Date: Sat, 3 Jun 2023 14:14:05 +0200 Subject: [PATCH] android: add option to install firmware --- .../fragments/HomeSettingsFragment.kt | 8 ++- .../IndeterminateProgressDialogFragment.kt | 36 ++++++++++ .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 65 +++++++++++++++++++ .../app/src/main/res/drawable/ic_firmware.xml | 10 +++ .../app/src/main/res/values/strings.xml | 6 ++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt create mode 100644 src/android/app/src/main/res/drawable/ic_firmware.xml 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 67bcf8491..cc4b0157b 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 @@ -19,10 +19,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController @@ -40,6 +40,7 @@ import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.ui.main.MainActivity +import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.GpuDriverHelper class HomeSettingsFragment : Fragment() { @@ -108,6 +109,11 @@ class HomeSettingsFragment : Fragment() { R.string.install_prod_keys_description, R.drawable.ic_unlock ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }, + HomeSetting( + R.string.install_firmware, + R.string.install_firmware_description, + R.drawable.ic_firmware + ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }, HomeSetting( R.string.about, R.string.about_description, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt new file mode 100644 index 000000000..edf7b8a3c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt @@ -0,0 +1,36 @@ +package org.yuzu.yuzu_emu.fragments + +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding + +class IndeterminateProgressDialogFragment : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val titleId = requireArguments().getInt(TITLE) + + val progressBinding = DialogProgressBarBinding.inflate(layoutInflater) + progressBinding.progressBar.isIndeterminate = true + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(titleId) + .setView(progressBinding.root) + .show() + } + + companion object { + const val TAG = "IndeterminateProgressDialogFragment" + + private const val TITLE = "Title" + + fun newInstance( + titleId: Int, + ): IndeterminateProgressDialogFragment { + val dialog = IndeterminateProgressDialogFragment() + val args = Bundle() + args.putInt(TITLE, titleId) + dialog.arguments = args + return dialog + } + } +} \ No newline at end of file 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 f8bca11bb..bb8311023 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 @@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.navigation.NavigationBarView +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -37,10 +38,13 @@ import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile +import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.utils.* +import java.io.File +import java.io.FilenameFilter import java.io.IOException class MainActivity : AppCompatActivity(), ThemeProvider { @@ -315,6 +319,67 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } + val getFirmware = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result == null) + return@registerForActivityResult + + val inputZip = contentResolver.openInputStream(result) + if (inputZip == null) { + Toast.makeText( + applicationContext, + getString(R.string.fatal_error), + Toast.LENGTH_LONG + ).show() + return@registerForActivityResult + } + + val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } + + val firmwarePath = + File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") + val cacheFirmwareDir = File("${cacheDir.path}/registered/") + + val installingFirmwareDialog = IndeterminateProgressDialogFragment.newInstance( + R.string.firmware_installing + ) + installingFirmwareDialog.isCancelable = false + installingFirmwareDialog.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) + + lifecycleScope.launch(Dispatchers.IO) { + try { + FileUtil.unzip(inputZip, cacheFirmwareDir) + val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 + val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 + if (unfilteredNumOfFiles != filteredNumOfFiles) { + withContext(Dispatchers.Main) { + installingFirmwareDialog.dismiss() + MessageDialogFragment.newInstance( + R.string.firmware_installed_failure, + R.string.firmware_installed_failure_description + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } + } else { + firmwarePath.deleteRecursively() + cacheFirmwareDir.copyRecursively(firmwarePath, true) + withContext(Dispatchers.Main) { + installingFirmwareDialog.dismiss() + Toast.makeText( + applicationContext, + getString(R.string.save_file_imported_success), + Toast.LENGTH_LONG + ).show() + } + } + } catch (e: Exception) { + Toast.makeText(applicationContext, getString(R.string.fatal_error), Toast.LENGTH_LONG) + .show() + } finally { + cacheFirmwareDir.deleteRecursively() + } + } + } + val getAmiiboKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> if (result == null) diff --git a/src/android/app/src/main/res/drawable/ic_firmware.xml b/src/android/app/src/main/res/drawable/ic_firmware.xml new file mode 100644 index 000000000..61f3485e4 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_firmware.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index fc24e27f5..4b3bfcf9d 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -96,6 +96,12 @@ The first subfolder name must be the title ID of the game. Import Export + Install firmware + Required to boot some games + Installing firmware + Firmware installed successfully + Firmware installation failed. + Check that the ZIP contains a firmware. Gaia isn\'t real