From 516455209e92018a9d01a98e733619e139594544 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Thu, 6 Nov 2025 14:58:28 +0300 Subject: [PATCH] SASPlanet sqlite parser --- TODO | 2 +- .../com/mirenkov/ktheightmap/KhmParser.kt | 1 + .../mirenkov/ktheightmap/SettingsActivity.kt | 15 +++- .../ktheightmap/parser/SasSQLiteParser.kt | 82 +++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/mirenkov/ktheightmap/parser/SasSQLiteParser.kt diff --git a/TODO b/TODO index bd8cf59..612015c 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,5 @@ functionality: - sasplanet zipped sqlitedbs parser + (done) sasplanet zipped sqlitedbs parser refactor: extract file opener from settings activity extract zip parser from settings activity diff --git a/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt b/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt index 6081679..68b6a76 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt @@ -29,6 +29,7 @@ class KhmParser { ctx.openFileOutput(HEIGHT_FILE, Context.MODE_PRIVATE).use { it.write(inp.readBytes()) } + inp.close() } private fun getOffset(header: HeightInfo, x: Int, y: Int): Int { diff --git a/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt b/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt index 7e2985f..64c3e66 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt @@ -3,6 +3,7 @@ package com.mirenkov.ktheightmap import android.app.Application import android.os.Bundle import android.util.Log +import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -22,10 +23,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel +import com.mirenkov.ktheightmap.parser.SasSQLiteParser import com.mirenkov.ktheightmap.ui.theme.KtHeightMapTheme import com.nareshchocha.filepickerlibrary.FilePickerResultContracts import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig import com.nareshchocha.filepickerlibrary.models.FilePickerResult +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import java.io.FileInputStream @@ -102,6 +105,16 @@ fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher = coroutineScope { fun processEntry(zis: ZipInputStream, entry: ZipEntry): Tile { val ba = ByteArray(entry.size.toInt()) var offset = 0 - var size = zis.read(ba, 0, ba.size) + var size = zis.read(ba) while (size > 0) { offset += size size = zis.read(ba, offset, ba.size - offset) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/parser/SasSQLiteParser.kt b/app/src/main/java/com/mirenkov/ktheightmap/parser/SasSQLiteParser.kt new file mode 100644 index 0000000..deb71f1 --- /dev/null +++ b/app/src/main/java/com/mirenkov/ktheightmap/parser/SasSQLiteParser.kt @@ -0,0 +1,82 @@ +package com.mirenkov.ktheightmap.parser + +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.util.Log +import com.mirenkov.ktheightmap.TAG +import com.mirenkov.ktheightmap.Tile +import com.mirenkov.ktheightmap.TileRepository +import com.mirenkov.ktheightmap.toBase64 +import java.io.FileInputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream + +// Parser for zipped sas.planet SQLite +class SasSQLiteParser { + companion object { + private const val TEMP_DB_NAME: String = "temp.db" + + fun processZip(filePath: String, repo: TileRepository, ctx: Context) { + ZipInputStream(FileInputStream(filePath)).use { zis -> + var entry = zis.nextEntry + while(true) { + if (entry.name.endsWith(".sqlitedb")) { + processSqlite(zis, entry, repo, ctx) + } + entry = zis.nextEntry + if (entry == null) + break + } + } + } + + private fun processSqlite(zis: ZipInputStream, entry: ZipEntry, repo: TileRepository, ctx: Context) { + val ba = ByteArray(entry.size.toInt()) + var offset = 0 + var size = zis.read(ba) + while (size > 0) { + offset += size + size = zis.read(ba, offset, ba.size - offset) + } + + Log.i(TAG, entry.name) + Log.i(TAG, entry.size.toString()) + Log.i(TAG, entry.compressedSize.toString()) + + val level = Regex("(?<=z)(\\d+)").find(entry.name)!!.value.toInt() + + ctx.openFileOutput(TEMP_DB_NAME, Context.MODE_PRIVATE).use { + it.write(ba) + } + + val path = ctx.getFileStreamPath(TEMP_DB_NAME).path + val db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY) + + val columns: Array = arrayOf( "x", "y", "b" ) + db.query("t", columns, null, null, null, null, null, null).use { + assert(it.moveToFirst()) + assert(it.getType(0) == Cursor.FIELD_TYPE_INTEGER) + assert(it.getType(1) == Cursor.FIELD_TYPE_INTEGER) + assert(it.getType(2) == Cursor.FIELD_TYPE_BLOB) + processTile(it, repo, level) + while(it.moveToNext()) { + processTile(it, repo, level) + } + } + + db.close() + } + + private fun processTile(it: Cursor, repo: TileRepository, level: Int) { + val x = it.getInt(0) + val y = it.getInt(1) + val blob = it.getBlob(2) + val base64 = blob.toBase64() + val tile = Tile(x, y, level, base64) + val checkedTile = repo.getTile(x, y, level) + if (checkedTile == null) + repo.pushTile(tile) + } + } +} \ No newline at end of file