android: add option to install firmware

master
PabloG02 2023-06-03 14:14:05 +07:00
parent 19674ec78d
commit 5435f0be5e
5 changed files with 124 additions and 1 deletions

@ -19,10 +19,10 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController 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.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverHelper
class HomeSettingsFragment : Fragment() { class HomeSettingsFragment : Fragment() {
@ -108,6 +109,11 @@ class HomeSettingsFragment : Fragment() {
R.string.install_prod_keys_description, R.string.install_prod_keys_description,
R.drawable.ic_unlock R.drawable.ic_unlock
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }, ) { 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( HomeSetting(
R.string.about, R.string.about,
R.string.about_description, R.string.about_description,

@ -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
}
}
}

@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext 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.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 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.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
import java.io.File
import java.io.FilenameFilter
import java.io.IOException import java.io.IOException
class MainActivity : AppCompatActivity(), ThemeProvider { 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 = val getAmiiboKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) if (result == null)

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,167 103.5,143.5Q127,120 160,120L720,120Q753,120 776.5,143.5Q800,167 800,200L800,280L840,280Q857,280 868.5,291.5Q880,303 880,320Q880,337 868.5,348.5Q857,360 840,360L800,360L800,440L840,440Q857,440 868.5,451.5Q880,463 880,480Q880,497 868.5,508.5Q857,520 840,520L800,520L800,600L840,600Q857,600 868.5,611.5Q880,623 880,640Q880,657 868.5,668.5Q857,680 840,680L800,680L800,760Q800,793 776.5,816.5Q753,840 720,840L160,840ZM160,760L720,760Q720,760 720,760Q720,760 720,760L720,200Q720,200 720,200Q720,200 720,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760ZM280,680L400,680Q417,680 428.5,668.5Q440,657 440,640L440,560Q440,543 428.5,531.5Q417,520 400,520L280,520Q263,520 251.5,531.5Q240,543 240,560L240,640Q240,657 251.5,668.5Q263,680 280,680ZM520,400L600,400Q617,400 628.5,388.5Q640,377 640,360L640,320Q640,303 628.5,291.5Q617,280 600,280L520,280Q503,280 491.5,291.5Q480,303 480,320L480,360Q480,377 491.5,388.5Q503,400 520,400ZM280,480L400,480Q417,480 428.5,468.5Q440,457 440,440L440,320Q440,303 428.5,291.5Q417,280 400,280L280,280Q263,280 251.5,291.5Q240,303 240,320L240,440Q240,457 251.5,468.5Q263,480 280,480ZM520,680L600,680Q617,680 628.5,668.5Q640,657 640,640L640,480Q640,463 628.5,451.5Q617,440 600,440L520,440Q503,440 491.5,451.5Q480,463 480,480L480,640Q480,657 491.5,668.5Q503,680 520,680ZM160,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760L160,760Q160,760 160,760Q160,760 160,760L160,200Q160,200 160,200Q160,200 160,200Z"/>
</vector>

@ -96,6 +96,12 @@
<string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
<string name="import_saves">Import</string> <string name="import_saves">Import</string>
<string name="export_saves">Export</string> <string name="export_saves">Export</string>
<string name="install_firmware">Install firmware</string>
<string name="install_firmware_description">Required to boot some games</string>
<string name="firmware_installing">Installing firmware</string>
<string name="firmware_installed_success">Firmware installed successfully</string>
<string name="firmware_installed_failure">Firmware installation failed.</string>
<string name="firmware_installed_failure_description">Check that the ZIP contains a firmware.</string>
<!-- About screen strings --> <!-- About screen strings -->
<string name="gaia_is_not_real">Gaia isn\'t real</string> <string name="gaia_is_not_real">Gaia isn\'t real</string>