From 7c7768fba414ff6528f258844d5de5dfc12ab82b Mon Sep 17 00:00:00 2001 From: kichikuou Date: Wed, 10 Apr 2024 14:11:06 +0900 Subject: [PATCH] Android: Improve installation when game is in non-root directory of ZIP Launcher.kt extracts given ZIP file to the internal storage, preserving its directory structure. Before this change, if the game files were in a subdirectory, an xsystem35.gr file was generated containing the path to the game files. However, with this method, .xsys35rc and some data files (such as map files of Kaeru Nyo) could not be loaded. After this change, Launcher.kt will no longer generate the xsystem35.gr file. Instead, it records the directory where the game is located in game_directory.txt, and specify that directory in -gamedir to launch xsystem35. Fixes #51. --- .../kichikuou/xsystem35/GameActivity.kt | 3 + .../io/github/kichikuou/xsystem35/Launcher.kt | 90 +++++++------------ .../kichikuou/xsystem35/LauncherActivity.kt | 1 + 3 files changed, 38 insertions(+), 56 deletions(-) diff --git a/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt b/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt index 57344f3..b444082 100644 --- a/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt +++ b/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt @@ -30,9 +30,11 @@ import java.io.IOException // Intent for this activity must have the following extra: // - EXTRA_GAME_ROOT (string): A path to the game installation. +// - EXTRA_SAVE_DIRECTORY (string): A path to the save data directory. class GameActivity : SDLActivity() { companion object { const val EXTRA_GAME_ROOT = "GAME_ROOT" + const val EXTRA_SAVE_DIRECTORY = "SAVE_DIRECTORY" const val EXTRA_ARCHIVE_NAME = "ARCHIVE_NAME" } @@ -61,6 +63,7 @@ class GameActivity : SDLActivity() { override fun getArguments(): Array { return arrayOf( "-gamedir", intent.getStringExtra(EXTRA_GAME_ROOT)!!, + "-savedir", intent.getStringExtra(EXTRA_SAVE_DIRECTORY)!!, "-playlist", Launcher.PLAYLIST_FILE) } diff --git a/android/app/src/main/java/io/github/kichikuou/xsystem35/Launcher.kt b/android/app/src/main/java/io/github/kichikuou/xsystem35/Launcher.kt index 1359cca..88f0ee4 100644 --- a/android/app/src/main/java/io/github/kichikuou/xsystem35/Launcher.kt +++ b/android/app/src/main/java/io/github/kichikuou/xsystem35/Launcher.kt @@ -25,9 +25,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.* -import java.lang.StringBuilder import java.nio.charset.Charset -import java.util.* import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream @@ -41,11 +39,11 @@ interface LauncherObserver { fun onInstallFailure(msgId: Int) } -private const val SAVE_DIR = "save" - class Launcher private constructor(private val rootDir: File) { companion object { + const val SAVE_DIR = "save" const val TITLE_FILE = "title.txt" + const val GAMEDIR_FILE = "game_directory.txt" const val PLAYLIST_FILE = "playlist2.txt" const val OLD_PLAYLIST_FILE = "playlist.txt" @@ -79,14 +77,14 @@ class Launcher private constructor(private val rootDir: File) { isInstalling = true GlobalScope.launch(Dispatchers.Main) { try { - withContext(Dispatchers.IO) { + val gameDir = withContext(Dispatchers.IO) { extractFiles(input, dir) { msg -> GlobalScope.launch(Dispatchers.Main) { observer?.onInstallProgress(msg) } } } - observer?.onInstallSuccess(dir, archiveName) + observer?.onInstallSuccess(gameDir, archiveName) } catch (e: InstallFailureException) { observer?.onInstallFailure(e.msgId) } catch (e: Exception) { @@ -114,9 +112,11 @@ class Launcher private constructor(private val rootDir: File) { continue } try { - val titleFile = File(path, TITLE_FILE) + val gameDirFile = File(path, GAMEDIR_FILE) + val gamePath = if (gameDirFile.exists()) File(path, gameDirFile.readText()) else path + val titleFile = File(gamePath, TITLE_FILE) val title = titleFile.readText() - games.add(Entry(path, title, titleFile.lastModified())) + games.add(Entry(gamePath, title, titleFile.lastModified())) migratePlaylist(path) } catch (e: IOException) { // Incomplete game installation. Delete it. @@ -179,7 +179,7 @@ class Launcher private constructor(private val rootDir: File) { } } - private fun extractFiles(input: InputStream, outDir: File, progressCallback: (String) -> Unit) { + private fun extractFiles(input: InputStream, outDir: File, progressCallback: (String) -> Unit): File { val configWriter = GameConfigWriter() val hadDecodeError = forEachZipEntry(input) { zipEntry, zip -> Log.i("extractFiles", zipEntry.name) @@ -193,12 +193,13 @@ class Launcher private constructor(private val rootDir: File) { } configWriter.maybeAdd(zipEntry.name) } - if (!configWriter.readyToWrite()) { + if (!configWriter.ready) { if (hadDecodeError) throw InstallFailureException(R.string.unsupported_zip) throw InstallFailureException(R.string.cannot_find_ald) } configWriter.write(outDir) + return configWriter.gameDir?.let { File(outDir, it) } ?: outDir } // Xsystem35-sdl2 <=2.2.0 had a bug where playlist had an extra empty line at @@ -217,66 +218,43 @@ class Launcher private constructor(private val rootDir: File) { class InstallFailureException(val msgId: Int) : Exception() - // A helper class which generates xsystem35.gr and playlist.txt in the game root directory. + // A helper class which generates GAMEDIR_FILE and PLAYLIST_FILE. private class GameConfigWriter { - private val grb = StringBuilder() - private var basename: String? = null - private val aldRegex = """(.*?)([a-z])([a-z])\.(ald|wai|bgi)""".toRegex(RegexOption.IGNORE_CASE) - private val aldType = mapOf( - "b" to "BGM", - "d" to "Data", - "g" to "Graphics", - "m" to "Midi", - "r" to "Resource", - "s" to "Scenario", - "w" to "Wave") - private val specialResources = mapOf( - "system39.ain" to "Ain", - "system39.ini" to "Init", - "sactefam.kld" to "SACT01" - ) + var ready = false + private set + var gameDir: String? = null + private set + private val aldRegex = """.*?s[a-z]\.ald""".toRegex(RegexOption.IGNORE_CASE) private val audioRegex = """.*?(\d+)\.(wav|mp3|ogg)""".toRegex(RegexOption.IGNORE_CASE) private val audioFiles: Array = arrayOfNulls(100) fun maybeAdd(path: String) { - val name = File(path).name - - specialResources[name.lowercase(Locale.US)]?.let { - grb.appendLine("$it $path") - return + aldRegex.matchEntire(path)?.let { + gameDir = File(path).parent + ready = true } - aldRegex.matchEntire(name)?.let { - val ext = it.groupValues[4].uppercase(Locale.US) - val type = if (ext == "ALD") { - aldType[it.groupValues[2].lowercase(Locale.US)] - } else { - ext - } - val id = it.groupValues[3].uppercase(Locale.US) - if (type != null) { - grb.appendLine("$type$id $path") - basename = it.groupValues[1] - } - } - audioRegex.matchEntire(name)?.let { + audioRegex.matchEntire(path)?.let { val track = it.groupValues[1].toInt() if (0 < track && track <= audioFiles.size) audioFiles[track - 1] = path } } - fun readyToWrite() = basename != null - fun write(outDir: File) { - for (id in 'A' .. 'Z') { - grb.appendLine("Save$id ../save/${basename}s${id.lowercase(Locale.US)}.asd") + // Generate GAMEDIR_FILE + gameDir?.let { + File(outDir, GAMEDIR_FILE).writeText(it) + } + // Generate PLAYLIST_FILE + val absGameDir = gameDir?.let { File(outDir, it) } ?: outDir + val playlistFile = File(absGameDir, PLAYLIST_FILE) + if (!playlistFile.exists()) { + val prefixToRemove = gameDir?.let { "$it/" } ?: "" + val playlist = audioFiles.joinToString("\n") { + it?.removePrefix(prefixToRemove) ?: "" + }.trimEnd('\n') + playlistFile.writeText(playlist) } - val gr = grb.toString() - Log.i("xsystem35.gr", gr) - File(outDir, "xsystem35.gr").writeText(gr) - - val playlist = audioFiles.joinToString("\n") { it ?: "" }.trimEnd('\n') - File(outDir, PLAYLIST_FILE).writeText(playlist) } } } diff --git a/android/app/src/main/java/io/github/kichikuou/xsystem35/LauncherActivity.kt b/android/app/src/main/java/io/github/kichikuou/xsystem35/LauncherActivity.kt index d3b43d5..e128ff8 100644 --- a/android/app/src/main/java/io/github/kichikuou/xsystem35/LauncherActivity.kt +++ b/android/app/src/main/java/io/github/kichikuou/xsystem35/LauncherActivity.kt @@ -180,6 +180,7 @@ class LauncherActivity : Activity(), LauncherObserver { val i = Intent() i.setClass(applicationContext, GameActivity::class.java) i.putExtra(GameActivity.EXTRA_GAME_ROOT, path.path) + i.putExtra(GameActivity.EXTRA_SAVE_DIRECTORY, File(filesDir, Launcher.SAVE_DIR).path) i.putExtra(GameActivity.EXTRA_ARCHIVE_NAME, archiveName) startActivity(i) }