From 909c381572f30219a6f10409cb90b301d2f8d6a6 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 30 Aug 2021 23:43:51 +0900 Subject: [PATCH] bytearray64reader wip --- src/net/torvald/terrarum/Terrarum.kt | 4 +- .../terrarum/modulebasegame/console/Load.kt | 47 ++++++++ .../terrarum/modulebasegame/console/Save.kt | 3 +- src/net/torvald/terrarum/serialise/Common.kt | 104 ++++++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/net/torvald/terrarum/modulebasegame/console/Load.kt diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index 7023deb58..fcee7a5f2 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -627,4 +627,6 @@ interface Id_UICanvasNullable { // haskell-inspired array selectors // head and last use first() and last() fun Array.tail() = this.sliceArray(1 until this.size) -fun Array.init() = this.sliceArray(0 until this.lastIndex) \ No newline at end of file +fun Array.init() = this.sliceArray(0 until this.lastIndex) +fun List.tail() = this.subList(1, this.size) +fun List.init() = this.subList(0, this.lastIndex) \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/console/Load.kt b/src/net/torvald/terrarum/modulebasegame/console/Load.kt new file mode 100644 index 000000000..11f1507c3 --- /dev/null +++ b/src/net/torvald/terrarum/modulebasegame/console/Load.kt @@ -0,0 +1,47 @@ +package net.torvald.terrarum.modulebasegame.console + +import com.badlogic.gdx.utils.JsonValue +import net.torvald.terrarum.AppLoader +import net.torvald.terrarum.Terrarum +import net.torvald.terrarum.console.ConsoleCommand +import net.torvald.terrarum.console.Echo +import net.torvald.terrarum.modulebasegame.TerrarumIngame +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskEntry +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.EntryFile +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil +import net.torvald.terrarum.serialise.* +import java.io.File +import java.io.IOException + +/** + * Created by minjaesong on 2021-08-30. + */ +object Load : ConsoleCommand { + + override fun execute(args: Array) { + if (args.size == 2) { + try { + val charset = Common.CHARSET + val file = File(AppLoader.defaultDir + "Exports/${args[1]}") + val disk = VDUtil.readDiskArchive(file, charset = charset) + + val metaFile = VDUtil.getFile(disk, VDUtil.VDPath("savegame", charset))!! + val metaReader = ByteArray64Reader(metaFile.contents.serialize().array) + val meta = Common.jsoner.fromJson(JsonValue::class.java, metaReader) + + } + catch (e: IOException) { + Echo("Load: IOException raised.") + e.printStackTrace() + } + } + else { + printUsage() + } + } + + override fun printUsage() { + Echo("Usage: load ") + } + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/console/Save.kt b/src/net/torvald/terrarum/modulebasegame/console/Save.kt index f77a40e84..270b76d78 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/Save.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/Save.kt @@ -14,6 +14,7 @@ import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskEntry import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskEntryContent import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.EntryFile import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil +import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.WriteActor import net.torvald.terrarum.serialise.WriteMeta import net.torvald.terrarum.serialise.WriteWorld @@ -39,7 +40,7 @@ object Save : ConsoleCommand { val creation_t = VDUtil.currentUnixtime val time_t = VDUtil.currentUnixtime - val disk = VDUtil.createNewDisk(1L shl 60, savename, Charsets.UTF_8) + val disk = VDUtil.createNewDisk(1L shl 60, savename, Common.CHARSET) // NOTE: don't bother with the entryID of DiskEntries; it will be overwritten anyway diff --git a/src/net/torvald/terrarum/serialise/Common.kt b/src/net/torvald/terrarum/serialise/Common.kt index 92072afd8..7fcd7badd 100644 --- a/src/net/torvald/terrarum/serialise/Common.kt +++ b/src/net/torvald/terrarum/serialise/Common.kt @@ -11,11 +11,17 @@ import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64OutputStream +import net.torvald.terrarum.tail import net.torvald.terrarum.utils.* import org.apache.commons.codec.digest.DigestUtils +import java.io.Reader import java.io.Writer import java.math.BigInteger +import java.nio.CharBuffer import java.nio.channels.ClosedChannelException +import java.nio.charset.Charset +import java.nio.charset.CharsetDecoder +import java.nio.charset.UnsupportedCharsetException import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream @@ -29,6 +35,8 @@ object Common { const val COMP_GZIP = 1 const val COMP_LZMA = 2 + val CHARSET = Charsets.UTF_8 + /** dispose of the `offendingObject` after rejection! */ class BlockLayerHashMismatchError(val oldHash: String, val newHash: String, val offendingObject: BlockLayer) : Error("Old Hash $oldHash != New Hash $newHash") @@ -305,4 +313,100 @@ class ByteArray64Writer() : Writer() { override fun flush() {} fun toByteArray64() = if (closed) ba64 else throw IllegalAccessException("Writer not closed") +} + +class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() { + + private val acceptableCharsets = arrayOf(Charsets.UTF_8, Charset.forName("CP437")) + + init { + if (!acceptableCharsets.contains(charset)) + throw UnsupportedCharsetException(charset.name()) + } + + private var readCursor = 0L + private val remaining + get() = ba.size - readCursor + + /** + * U+0000 .. U+007F 0xxxxxxx + * U+0080 .. U+07FF 110xxxxx 10xxxxxx + * U+0800 .. U+FFFF 1110xxxx 10xxxxxx 10xxxxxx + * U+10000 .. U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + private fun utf8GetBytes(head: Byte) = when (head.toInt() and 255) { + in 0b11110_000..0b11110_111 -> 4 + in 0b1110_0000..0b1110_1111 -> 3 + in 0b110_00000..0b110_11111 -> 2 + in 0b0_0000000..0b0_1111111 -> 1 + else -> throw IllegalArgumentException("Invalid UTF-8 Character head byte: ${head.toInt() and 255}") + } + + /** + * @param list of bytes that encodes one unicode character. Get required byte length using [utf8GetBytes]. + * @return A codepoint of the character. + */ + private fun utf8decode(bytes0: List): Int { + val bytes = bytes0.map { it.toInt() and 255 } + var ret = when (bytes.size) { + 4 -> (bytes[0] and 7) shl 15 + 3 -> (bytes[0] and 15) shl 10 + 2 -> (bytes[0] and 31) shl 5 + 1 -> (bytes[0] and 127) + else -> throw IllegalArgumentException("Expected bytes size: 1..4, got ${bytes.size}") + } + bytes.tail().forEachIndexed { index, byte -> + ret = ret or (byte and 63).shl(5 * (2 - index)) + } + return ret + } + + override fun read(cbuf: CharArray, off: Int, len: Int): Int { + var readCount = 0 + + when (charset) { + Charsets.UTF_8 -> { + while (readCount < len && remaining > 0) { + val bbuf = (0..minOf(3L, remaining)).map { ba[readCursor + it] } + val codePoint = utf8decode(bbuf.subList(0, utf8GetBytes(bbuf[0]))) + + if (codePoint < 65536) { + cbuf[off + readCount] = codePoint.toChar() + readCount += 1 + readCursor += bbuf.size + } + else { + /* + * U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000 + * W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy + * W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx + */ + val surroLead = (0xD800 or codePoint.ushr(10)).toChar() + val surroTrail = (0xDC00 or codePoint.and(1023)).toChar() + + cbuf[off + readCount] = surroLead + cbuf[off + readCount + 1] = surroTrail + + readCount += 2 + readCursor + 4 + } + } + } + Charset.forName("CP437") -> { + for (i in 0 until minOf(len.toLong(), remaining)) { + cbuf[(off + i).toInt()] = ba[readCursor].toChar() + readCursor += 1 + readCount += 1 + } + } + else -> throw UnsupportedCharsetException(charset.name()) + } + + return readCount + } + + override fun close() { readCursor = 0L } + override fun reset() { readCursor = 0L } + override fun markSupported() = false + } \ No newline at end of file