|
|
@ -46,13 +46,21 @@ 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.model.TaskViewModel
|
|
|
|
import org.yuzu.yuzu_emu.utils.*
|
|
|
|
import org.yuzu.yuzu_emu.utils.*
|
|
|
|
|
|
|
|
import java.io.BufferedInputStream
|
|
|
|
|
|
|
|
import java.io.BufferedOutputStream
|
|
|
|
|
|
|
|
import java.io.FileOutputStream
|
|
|
|
|
|
|
|
import java.util.zip.ZipEntry
|
|
|
|
|
|
|
|
import java.util.zip.ZipInputStream
|
|
|
|
|
|
|
|
import java.util.zip.ZipOutputStream
|
|
|
|
|
|
|
|
|
|
|
|
class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
private lateinit var binding: ActivityMainBinding
|
|
|
|
private lateinit var binding: ActivityMainBinding
|
|
|
|
|
|
|
|
|
|
|
|
private val homeViewModel: HomeViewModel by viewModels()
|
|
|
|
private val homeViewModel: HomeViewModel by viewModels()
|
|
|
|
private val gamesViewModel: GamesViewModel by viewModels()
|
|
|
|
private val gamesViewModel: GamesViewModel by viewModels()
|
|
|
|
|
|
|
|
private val taskViewModel: TaskViewModel by viewModels()
|
|
|
|
|
|
|
|
|
|
|
|
override var themeId: Int = 0
|
|
|
|
override var themeId: Int = 0
|
|
|
|
|
|
|
|
|
|
|
@ -307,6 +315,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
fun processKey(result: Uri): Boolean {
|
|
|
|
fun processKey(result: Uri): Boolean {
|
|
|
|
if (FileUtil.getExtension(result) != "keys") {
|
|
|
|
if (FileUtil.getExtension(result) != "keys") {
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.reading_keys_failure,
|
|
|
|
titleId = R.string.reading_keys_failure,
|
|
|
|
descriptionId = R.string.install_prod_keys_failure_extension_description
|
|
|
|
descriptionId = R.string.install_prod_keys_failure_extension_description
|
|
|
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
|
|
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
|
|
@ -336,6 +345,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
return true
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.invalid_keys_error,
|
|
|
|
titleId = R.string.invalid_keys_error,
|
|
|
|
descriptionId = R.string.install_keys_failure_description,
|
|
|
|
descriptionId = R.string.install_keys_failure_description,
|
|
|
|
helpLinkId = R.string.dumping_keys_quickstart_link
|
|
|
|
helpLinkId = R.string.dumping_keys_quickstart_link
|
|
|
@ -376,6 +386,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
|
|
|
|
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
|
|
|
|
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
|
|
|
|
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.firmware_installed_failure,
|
|
|
|
titleId = R.string.firmware_installed_failure,
|
|
|
|
descriptionId = R.string.firmware_installed_failure_description
|
|
|
|
descriptionId = R.string.firmware_installed_failure_description
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -395,7 +406,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
IndeterminateProgressDialogFragment.newInstance(
|
|
|
|
IndeterminateProgressDialogFragment.newInstance(
|
|
|
|
this,
|
|
|
|
this,
|
|
|
|
R.string.firmware_installing,
|
|
|
|
R.string.firmware_installing,
|
|
|
|
task
|
|
|
|
task = task
|
|
|
|
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
|
|
|
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -407,6 +418,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
|
|
|
|
|
|
|
|
if (FileUtil.getExtension(result) != "bin") {
|
|
|
|
if (FileUtil.getExtension(result) != "bin") {
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.reading_keys_failure,
|
|
|
|
titleId = R.string.reading_keys_failure,
|
|
|
|
descriptionId = R.string.install_amiibo_keys_failure_extension_description
|
|
|
|
descriptionId = R.string.install_amiibo_keys_failure_extension_description
|
|
|
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
|
|
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
|
|
@ -434,6 +446,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
).show()
|
|
|
|
).show()
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.invalid_keys_error,
|
|
|
|
titleId = R.string.invalid_keys_error,
|
|
|
|
descriptionId = R.string.install_keys_failure_description,
|
|
|
|
descriptionId = R.string.install_keys_failure_description,
|
|
|
|
helpLinkId = R.string.dumping_keys_quickstart_link
|
|
|
|
helpLinkId = R.string.dumping_keys_quickstart_link
|
|
|
@ -583,12 +596,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
installResult.append(separator)
|
|
|
|
installResult.append(separator)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return@newInstance MessageDialogFragment.newInstance(
|
|
|
|
return@newInstance MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.install_game_content_failure,
|
|
|
|
titleId = R.string.install_game_content_failure,
|
|
|
|
descriptionString = installResult.toString().trim(),
|
|
|
|
descriptionString = installResult.toString().trim(),
|
|
|
|
helpLinkId = R.string.install_game_content_help_link
|
|
|
|
helpLinkId = R.string.install_game_content_help_link
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return@newInstance MessageDialogFragment.newInstance(
|
|
|
|
return@newInstance MessageDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
titleId = R.string.install_game_content_success,
|
|
|
|
titleId = R.string.install_game_content_success,
|
|
|
|
descriptionString = installResult.toString().trim()
|
|
|
|
descriptionString = installResult.toString().trim()
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -596,4 +611,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|
|
|
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
|
|
|
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val exportUserData = registerForActivityResult(
|
|
|
|
|
|
|
|
ActivityResultContracts.CreateDocument("application/zip")
|
|
|
|
|
|
|
|
) { result ->
|
|
|
|
|
|
|
|
if (result == null) {
|
|
|
|
|
|
|
|
return@registerForActivityResult
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IndeterminateProgressDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
|
|
|
|
R.string.exporting_user_data,
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
val zos = ZipOutputStream(
|
|
|
|
|
|
|
|
BufferedOutputStream(contentResolver.openOutputStream(result))
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
zos.use { stream ->
|
|
|
|
|
|
|
|
File(DirectoryInitialization.userDirectory!!).walkTopDown().forEach { file ->
|
|
|
|
|
|
|
|
if (taskViewModel.cancelled.value) {
|
|
|
|
|
|
|
|
return@newInstance R.string.user_data_export_cancelled
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!file.isDirectory) {
|
|
|
|
|
|
|
|
val newPath = file.path.substring(
|
|
|
|
|
|
|
|
DirectoryInitialization.userDirectory!!.length,
|
|
|
|
|
|
|
|
file.path.length
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
stream.putNextEntry(ZipEntry(newPath))
|
|
|
|
|
|
|
|
stream.write(file.readBytes())
|
|
|
|
|
|
|
|
stream.closeEntry()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return@newInstance getString(R.string.user_data_export_success)
|
|
|
|
|
|
|
|
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val importUserData =
|
|
|
|
|
|
|
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
|
|
|
|
|
|
|
if (result == null) {
|
|
|
|
|
|
|
|
return@registerForActivityResult
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IndeterminateProgressDialogFragment.newInstance(
|
|
|
|
|
|
|
|
this,
|
|
|
|
|
|
|
|
R.string.importing_user_data
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
val checkStream =
|
|
|
|
|
|
|
|
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
|
|
|
|
|
|
|
|
var isYuzuBackup = false
|
|
|
|
|
|
|
|
checkStream.use { stream ->
|
|
|
|
|
|
|
|
var ze: ZipEntry? = null
|
|
|
|
|
|
|
|
while (stream.nextEntry?.also { ze = it } != null) {
|
|
|
|
|
|
|
|
if (ze!!.name.trim() == "/config/config.ini") {
|
|
|
|
|
|
|
|
isYuzuBackup = true
|
|
|
|
|
|
|
|
return@use
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isYuzuBackup) {
|
|
|
|
|
|
|
|
return@newInstance getString(R.string.invalid_yuzu_backup)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val zis =
|
|
|
|
|
|
|
|
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
|
|
|
|
|
|
|
|
val userDirectory = File(DirectoryInitialization.userDirectory!!)
|
|
|
|
|
|
|
|
val canonicalPath = userDirectory.canonicalPath + '/'
|
|
|
|
|
|
|
|
zis.use { stream ->
|
|
|
|
|
|
|
|
var ze: ZipEntry? = stream.nextEntry
|
|
|
|
|
|
|
|
while (ze != null) {
|
|
|
|
|
|
|
|
val newFile = File(userDirectory, ze!!.name)
|
|
|
|
|
|
|
|
val destinationDirectory =
|
|
|
|
|
|
|
|
if (ze!!.isDirectory) newFile else newFile.parentFile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!newFile.canonicalPath.startsWith(canonicalPath)) {
|
|
|
|
|
|
|
|
throw SecurityException(
|
|
|
|
|
|
|
|
"Zip file attempted path traversal! ${ze!!.name}"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
|
|
|
|
|
|
|
|
throw IOException("Failed to create directory $destinationDirectory")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!ze!!.isDirectory) {
|
|
|
|
|
|
|
|
val buffer = ByteArray(8096)
|
|
|
|
|
|
|
|
var read: Int
|
|
|
|
|
|
|
|
BufferedOutputStream(FileOutputStream(newFile)).use { bos ->
|
|
|
|
|
|
|
|
while (zis.read(buffer).also { read = it } != -1) {
|
|
|
|
|
|
|
|
bos.write(buffer, 0, read)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ze = stream.nextEntry
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reinitialize relevant data
|
|
|
|
|
|
|
|
NativeLibrary.initializeEmulation()
|
|
|
|
|
|
|
|
gamesViewModel.reloadGames(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return@newInstance getString(R.string.user_data_import_success)
|
|
|
|
|
|
|
|
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|