mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-10-04 04:38:03 +03:00
Android: Export / import save files
Now save files can be exported to ZIP / imported from ZIP by the option menu of the launcher activity.
This commit is contained in:
@@ -25,7 +25,9 @@ import android.util.Log
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
private var gLauncher: Launcher? = null
|
||||
|
||||
@@ -36,6 +38,7 @@ interface LauncherObserver {
|
||||
fun onInstallFailure(msgId: Int)
|
||||
}
|
||||
|
||||
private const val SAVE_DIR = "save"
|
||||
private const val PROGRESS = 0
|
||||
private const val SUCCESS = 1
|
||||
private const val FAILURE = 2
|
||||
@@ -107,7 +110,7 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
for (path in rootDir.listFiles()) {
|
||||
if (!path.isDirectory)
|
||||
continue
|
||||
if (path.name == "save") {
|
||||
if (path.name == SAVE_DIR) {
|
||||
saveDirFound = true
|
||||
continue
|
||||
}
|
||||
@@ -120,7 +123,7 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
}
|
||||
}
|
||||
if (!saveDirFound) {
|
||||
File(rootDir, "save").mkdir()
|
||||
File(rootDir, SAVE_DIR).mkdir()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,28 +137,65 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
}
|
||||
}
|
||||
|
||||
// Throws IOException
|
||||
fun exportSaveData(output: OutputStream) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
ZipOutputStream(output.buffered(), Charset.forName("Shift_JIS"))
|
||||
} else {
|
||||
ZipOutputStream(output.buffered())
|
||||
}.use { zip ->
|
||||
for (path in File(rootDir, SAVE_DIR).listFiles()) {
|
||||
if (path.isDirectory || path.name.endsWith(".asd."))
|
||||
continue
|
||||
val pathInZip = "${SAVE_DIR}/${path.name}"
|
||||
Log.i("exportSaveData", pathInZip)
|
||||
zip.putNextEntry(ZipEntry(pathInZip))
|
||||
path.inputStream().buffered().use {
|
||||
it.copyTo(zip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importSaveData(input: InputStream): Int? {
|
||||
try {
|
||||
var imported = false
|
||||
forEachZipEntry(input) { zipEntry, zip ->
|
||||
// Process only files directly under save/
|
||||
if (zipEntry.isDirectory || !zipEntry.name.startsWith("save/") ||
|
||||
zipEntry.name.count{it == '/'} != 1)
|
||||
return@forEachZipEntry
|
||||
Log.i("importSaveData", zipEntry.name)
|
||||
FileOutputStream(File(rootDir, zipEntry.name)).buffered().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
imported = true
|
||||
}
|
||||
return if (imported) null else R.string.no_data_to_import
|
||||
} catch (e: UTFDataFormatException) {
|
||||
// Attempted to read Shift_JIS zip in Android < 7
|
||||
return R.string.unsupported_zip
|
||||
} catch (e: IOException) {
|
||||
Log.e("launcher", "Failed to extract ZIP", e)
|
||||
return R.string.zip_extraction_error
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractFiles(input: InputStream, outDir: File, handler: Handler) {
|
||||
try {
|
||||
val configWriter = GameConfigWriter()
|
||||
val zip = if (Build.VERSION.SDK_INT >= 24) {
|
||||
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))
|
||||
} else {
|
||||
ZipInputStream(input.buffered())
|
||||
}
|
||||
while (true) {
|
||||
val zipEntry = zip.nextEntry ?: break
|
||||
Log.v("extractFiles", zipEntry.name)
|
||||
forEachZipEntry(input) { zipEntry, zip ->
|
||||
Log.i("extractFiles", zipEntry.name)
|
||||
val path = File(outDir, zipEntry.name)
|
||||
if (zipEntry.isDirectory)
|
||||
continue
|
||||
return@forEachZipEntry
|
||||
path.parentFile.mkdirs()
|
||||
handler.sendMessage(handler.obtainMessage(PROGRESS, zipEntry.name))
|
||||
val output = FileOutputStream(path).buffered()
|
||||
zip.copyTo(output)
|
||||
output.close()
|
||||
FileOutputStream(path).buffered().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
configWriter.maybeAdd(zipEntry.name)
|
||||
}
|
||||
zip.close()
|
||||
configWriter.write(outDir)
|
||||
handler.sendMessage(handler.obtainMessage(SUCCESS, outDir))
|
||||
} catch (e: InstallFailureException) {
|
||||
@@ -195,7 +235,7 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
return
|
||||
}
|
||||
aldRegex.matchEntire(name)?.let {
|
||||
val type = resourceType[it.groupValues[2]]
|
||||
val type = resourceType[it.groupValues[2].toLowerCase(Locale.US)]
|
||||
val id = it.groupValues[3].toUpperCase(Locale.US)
|
||||
if (type != null) {
|
||||
grLines.add("$type$id $path")
|
||||
@@ -216,7 +256,7 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
}
|
||||
grLines.add("")
|
||||
val gr = grLines.joinToString("\n")
|
||||
Log.d("xsystem35.gr", gr)
|
||||
Log.i("xsystem35.gr", gr)
|
||||
File(outDir, "xsystem35.gr").writeText(gr)
|
||||
|
||||
val playlist = audioFiles.joinToString("\n") { it ?: "" }.trimEnd('\n')
|
||||
@@ -224,3 +264,17 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun forEachZipEntry(input: InputStream, action: (ZipEntry, ZipInputStream) -> Unit) {
|
||||
val zip = if (Build.VERSION.SDK_INT >= 24) {
|
||||
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))
|
||||
} else {
|
||||
ZipInputStream(input.buffered())
|
||||
}
|
||||
zip.use {
|
||||
while (true) {
|
||||
val zipEntry = zip.nextEntry ?: break
|
||||
action(zipEntry, zip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,21 @@ package io.github.kichikuou.xsystem35
|
||||
import android.app.*
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.Toast
|
||||
import java.io.*
|
||||
|
||||
private const val CONTENT_TYPE_ZIP = "application/zip"
|
||||
private const val INSTALL_REQUEST = 1
|
||||
private const val SAVEDATA_EXPORT_REQUEST = 2
|
||||
private const val SAVEDATA_IMPORT_REQUEST = 3
|
||||
|
||||
class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, LauncherObserver {
|
||||
private lateinit var launcher: Launcher
|
||||
private var progressDialog: ProgressDialogFragment? = null
|
||||
@@ -54,8 +63,8 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
startGame(launcher.games[position].path)
|
||||
} else {
|
||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
||||
i.type = "application/zip"
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), 0)
|
||||
i.type = CONTENT_TYPE_ZIP
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), INSTALL_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +79,59 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.launcher_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.export_savedata -> {
|
||||
val i = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
i.type = CONTENT_TYPE_ZIP
|
||||
i.putExtra(Intent.EXTRA_TITLE, "savedata.zip")
|
||||
startActivityForResult(i, SAVEDATA_EXPORT_REQUEST)
|
||||
true
|
||||
}
|
||||
R.id.import_savedata -> {
|
||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
||||
i.type = CONTENT_TYPE_ZIP
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)),
|
||||
SAVEDATA_IMPORT_REQUEST)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode != RESULT_OK)
|
||||
return
|
||||
val uri = data?.data ?: return
|
||||
val input = contentResolver.openInputStream(uri) ?: return
|
||||
showProgressDialog()
|
||||
launcher.install(input)
|
||||
when (requestCode) {
|
||||
INSTALL_REQUEST -> {
|
||||
val input = contentResolver.openInputStream(uri) ?: return
|
||||
showProgressDialog()
|
||||
launcher.install(input)
|
||||
}
|
||||
SAVEDATA_EXPORT_REQUEST -> try {
|
||||
launcher.exportSaveData(contentResolver.openOutputStream(uri)!!)
|
||||
Toast.makeText(this, R.string.save_data_export_success, Toast.LENGTH_SHORT).show()
|
||||
} catch (e: IOException) {
|
||||
Log.e("launcher", "Failed to export savedata", e)
|
||||
errorDialog(R.string.save_data_export_error)
|
||||
}
|
||||
SAVEDATA_IMPORT_REQUEST -> {
|
||||
val input = contentResolver.openInputStream(uri) ?: return
|
||||
val errMsgId = launcher.importSaveData(input)
|
||||
if (errMsgId == null) {
|
||||
Toast.makeText(this, R.string.save_data_import_success, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
errorDialog(errMsgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onGameListChange() {
|
||||
|
||||
Reference in New Issue
Block a user