Use the title of the game as the name of the zip.

This zip will have the structure gameTitle.zip/titleId/...
This commit is contained in:
PabloG02 2023-04-09 22:46:00 +02:00 committed by Billy Laws
parent 3dbd47082d
commit 71a0033f5b
2 changed files with 47 additions and 27 deletions

View File

@ -34,10 +34,12 @@ 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
import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
/** /**
@ -65,20 +67,31 @@ class AppDialog : BottomSheetDialogFragment() {
private val savesFolderRoot by lazy { "${requireContext().getPublicFilesDir().canonicalPath}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001/" } private val savesFolderRoot by lazy { "${requireContext().getPublicFilesDir().canonicalPath}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001/" }
private val documentPicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) { private val documentPicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri -> it?.let { uri ->
if (uri.toString().takeLast(20).removeSuffix(".zip") == item.titleId) { try {
val saveFolder = File(savesFolderRoot + item.titleId) val savesFolder = File(savesFolderRoot)
val inputZip = requireContext().contentResolver.openInputStream(uri) var inputZip = requireContext().contentResolver.openInputStream(uri)
var validZip = false
// A TitleID must be the first folder name inside the zip in order to be considered valid.
if (inputZip != null) { if (inputZip != null) {
CoroutineScope(Dispatchers.IO).launch { ZipInputStream(BufferedInputStream(inputZip)).use { zis ->
ZipUtils.unzip(inputZip, saveFolder) validZip = Regex("^0100[0-9A-Fa-f]{12}/\$").matches(zis.nextEntry.name)
withContext(Dispatchers.Main){
binding.deleteSave.isEnabled = true
binding.exportSave.isEnabled = true
}
} }
} }
} else { inputZip = requireContext().contentResolver.openInputStream(uri)
Snackbar.make(binding.root, getString(R.string.zip_with_save_must_have_name_equal_titleid), Snackbar.LENGTH_LONG).show() if (inputZip != null && validZip){
CoroutineScope(Dispatchers.IO).launch {
ZipUtils.unzip(inputZip, savesFolder)
withContext(Dispatchers.Main){
val isSaveFileOfThisGame = File("$savesFolderRoot${item.titleId}").exists()
binding.deleteSave.isEnabled = isSaveFileOfThisGame
binding.exportSave.isEnabled = isSaveFileOfThisGame
}
}
} else {
Snackbar.make(binding.root, getString(R.string.save_file_invalid_zip_structure), Snackbar.LENGTH_LONG).show()
}
} catch (e: Exception) {
Snackbar.make(binding.root, getString(R.string.error), Snackbar.LENGTH_LONG).show()
} }
} }
} }
@ -162,24 +175,31 @@ class AppDialog : BottomSheetDialogFragment() {
binding.exportSave.isEnabled = saveExists binding.exportSave.isEnabled = saveExists
binding.exportSave.setOnClickListener { binding.exportSave.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val saveFolder = File(saveFolderPath) try {
//val outputZipFile = File.createTempFile("out", ".zip") val saveFolder = File(saveFolderPath)
val outputZipFile = File("$saveFolderPath.zip") val outputZipFile = File("$savesFolderRoot${item.title}.zip")
if (outputZipFile.exists()) outputZipFile.delete() outputZipFile.delete()
outputZipFile.createNewFile() outputZipFile.createNewFile()
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos -> ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
saveFolder.walkTopDown().forEach { file -> saveFolder.walkTopDown().forEach { file ->
val zipFileName = file.absolutePath.removePrefix(saveFolder.absolutePath).removePrefix("/") val zipFileName = file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
if (zipFileName == "") return@forEach if (zipFileName == "") return@forEach
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
zos.putNextEntry(entry) zos.putNextEntry(entry)
if (file.isFile) { if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) } file.inputStream().use { fis -> fis.copyTo(zos) }
}
} }
} }
} catch (e: Exception) {
withContext(Dispatchers.Main){
Snackbar.make(binding.root, e.message as CharSequence, Snackbar.LENGTH_LONG).show()
}
return@launch
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val file = DocumentFile.fromSingleUri(requireContext(), DocumentsContract.buildDocumentUri(DocumentsProvider.AUTHORITY, "${DocumentsProvider.ROOT_ID}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001/${item.titleId}.zip"))!! val file = DocumentFile.fromSingleUri(requireContext(), DocumentsContract.buildDocumentUri(DocumentsProvider.AUTHORITY, "${DocumentsProvider.ROOT_ID}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001/${item.title}.zip"))!!
val intent = Intent(Intent.ACTION_SEND) val intent = Intent(Intent.ACTION_SEND)
.setDataAndType(file.uri, "application/zip") .setDataAndType(file.uri, "application/zip")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
@ -208,6 +228,6 @@ class AppDialog : BottomSheetDialogFragment() {
override fun onDestroyView(){ override fun onDestroyView(){
super.onDestroyView() super.onDestroyView()
File("$savesFolderRoot${item.titleId}.zip").delete() File("$savesFolderRoot${item.title}.zip").delete()
} }
} }

View File

@ -283,5 +283,5 @@
<string name="action_irreversible">This action is irreversible</string> <string name="action_irreversible">This action is irreversible</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="no">No</string> <string name="no">No</string>
<string name="zip_with_save_must_have_name_equal_titleid">Zip file must have as name the TitleID of the game</string> <string name="save_file_invalid_zip_structure">Failed to unzip the provided save file</string>
</resources> </resources>