mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
working ByteArray64Reader (UTF-8 compliant with proper surrogate pairing)
This commit is contained in:
@@ -26,7 +26,7 @@ object Load : ConsoleCommand {
|
||||
val disk = VDUtil.readDiskArchive(file, charset = charset)
|
||||
|
||||
val metaFile = VDUtil.getFile(disk, VDUtil.VDPath("savegame", charset))!!
|
||||
val metaReader = ByteArray64Reader(metaFile.contents.serialize().array)
|
||||
val metaReader = ByteArray64Reader(metaFile.contents.serialize().array, charset)
|
||||
val meta = Common.jsoner.fromJson(JsonValue::class.java, metaReader)
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.torvald.terrarum.modulebasegame.console
|
||||
|
||||
import net.torvald.terrarum.console.ConsoleCommand
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.serialise.ByteArray64Reader
|
||||
import net.torvald.terrarum.serialise.ByteArray64Writer
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2021-08-31.
|
||||
*/
|
||||
object ReaderTest : ConsoleCommand {
|
||||
override fun execute(args: Array<String>) {
|
||||
val textfile = File("./work_files/utftest.txt")
|
||||
val text = textfile.readText()
|
||||
|
||||
val writer = ByteArray64Writer(Charsets.UTF_8)
|
||||
writer.write(text); writer.flush(); writer.close()
|
||||
|
||||
val ba = writer.toByteArray64()
|
||||
|
||||
val reader = ByteArray64Reader(ba, Charsets.UTF_8)
|
||||
val readText = reader.readText(); reader.close()
|
||||
|
||||
println(readText)
|
||||
val outfile = File("./work_files/utftest-roundtrip.txt")
|
||||
outfile.writeText(readText, Charsets.UTF_8)
|
||||
}
|
||||
|
||||
override fun printUsage() {
|
||||
Echo("Usage: readertest")
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package net.torvald.terrarum.serialise
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonValue
|
||||
import com.badlogic.gdx.utils.JsonWriter
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.console.EchoError
|
||||
import net.torvald.terrarum.gameworld.BlockLayer
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
@@ -10,17 +11,14 @@ import net.torvald.terrarum.gameworld.WorldTime
|
||||
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
|
||||
@@ -273,7 +271,7 @@ object Common {
|
||||
}
|
||||
}
|
||||
|
||||
class ByteArray64Writer() : Writer() {
|
||||
class ByteArray64Writer(val charset: Charset) : Writer() {
|
||||
|
||||
private var closed = false
|
||||
private val ba64 = ByteArray64()
|
||||
@@ -288,7 +286,7 @@ class ByteArray64Writer() : Writer() {
|
||||
|
||||
override fun write(c: Int) {
|
||||
checkOpen()
|
||||
"${c.toChar()}".toByteArray().forEach { ba64.add(it) }
|
||||
"${c.toChar()}".toByteArray(charset).forEach { ba64.add(it) }
|
||||
}
|
||||
|
||||
override fun write(cbuf: CharArray) {
|
||||
@@ -298,7 +296,7 @@ class ByteArray64Writer() : Writer() {
|
||||
|
||||
override fun write(str: String) {
|
||||
checkOpen()
|
||||
str.toByteArray().forEach { ba64.add(it) }
|
||||
str.toByteArray(charset).forEach { ba64.add(it) }
|
||||
}
|
||||
|
||||
override fun write(cbuf: CharArray, off: Int, len: Int) {
|
||||
@@ -334,7 +332,7 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
|
||||
* 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) {
|
||||
private fun utf8GetCharLen(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
|
||||
@@ -343,37 +341,48 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list of bytes that encodes one unicode character. Get required byte length using [utf8GetBytes].
|
||||
* @param list of bytes that encodes one unicode character. Get required byte length using [utf8GetCharLen].
|
||||
* @return A codepoint of the character.
|
||||
*/
|
||||
private fun utf8decode(bytes0: List<Byte>): 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
|
||||
4 -> (bytes[0] and 7) shl 18
|
||||
3 -> (bytes[0] and 15) shl 12
|
||||
2 -> (bytes[0] and 31) shl 6
|
||||
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))
|
||||
bytes.tail().reversed().forEachIndexed { index, byte ->
|
||||
ret = ret or (byte and 63).shl(6 * index)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
private var surrogateLeftover = ' '
|
||||
|
||||
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 (surrogateLeftover != ' ') {
|
||||
cbuf[off + readCount] = surrogateLeftover
|
||||
|
||||
readCount += 1
|
||||
surrogateLeftover = ' '
|
||||
}
|
||||
else {
|
||||
val bbuf = (0 until minOf(4L, remaining)).map { ba[readCursor + it] }
|
||||
val charLen = utf8GetCharLen(bbuf[0])
|
||||
val codePoint = utf8decode(bbuf.subList(0, charLen))
|
||||
|
||||
if (codePoint < 65536) {
|
||||
cbuf[off + readCount] = codePoint.toChar()
|
||||
|
||||
readCount += 1
|
||||
readCursor += bbuf.size
|
||||
readCursor += charLen
|
||||
}
|
||||
else {
|
||||
/*
|
||||
@@ -381,14 +390,24 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
|
||||
* W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy
|
||||
* W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx
|
||||
*/
|
||||
val surroLead = (0xD800 or codePoint.ushr(10)).toChar()
|
||||
val surroTrail = (0xDC00 or codePoint.and(1023)).toChar()
|
||||
val codPoin = codePoint - 65536
|
||||
val surroLead = (0xD800 or codPoin.ushr(10)).toChar()
|
||||
val surroTrail = (0xDC00 or codPoin.and(1023)).toChar()
|
||||
|
||||
cbuf[off + readCount] = surroLead
|
||||
|
||||
if (off + readCount + 1 < cbuf.size) {
|
||||
cbuf[off + readCount + 1] = surroTrail
|
||||
|
||||
readCount += 2
|
||||
readCursor + 4
|
||||
readCursor += 4
|
||||
}
|
||||
else {
|
||||
readCount += 1
|
||||
readCursor += 4
|
||||
surrogateLeftover = surroTrail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +421,7 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
|
||||
else -> throw UnsupportedCharsetException(charset.name())
|
||||
}
|
||||
|
||||
return readCount
|
||||
return if (readCount == 0) -1 else readCount
|
||||
}
|
||||
|
||||
override fun close() { readCursor = 0L }
|
||||
|
||||
@@ -19,9 +19,12 @@ object WriteActor {
|
||||
}
|
||||
|
||||
fun encodeToByteArray64(actor: Actor): ByteArray64 {
|
||||
val ba = ByteArray64()
|
||||
this.invoke(actor).toByteArray().forEach { ba.add(it) }
|
||||
return ba
|
||||
val baw = ByteArray64Writer(Common.CHARSET)
|
||||
|
||||
Common.jsoner.toJson(actor, actor.javaClass, baw)
|
||||
baw.flush(); baw.close()
|
||||
|
||||
return baw.toByteArray64()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,28 +36,28 @@ open class WriteMeta(val ingame: TerrarumIngame) {
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
bytesToZipdStr(it.toString().toByteArray())
|
||||
bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
|
||||
}}",
|
||||
"items": "${StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("items/itemid.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
bytesToZipdStr(it.toString().toByteArray())
|
||||
bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
|
||||
}}",
|
||||
"wires": "${StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("wires/wires.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
bytesToZipdStr(it.toString().toByteArray())
|
||||
bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
|
||||
}}",
|
||||
"materials": "${StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("materials/materials.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
bytesToZipdStr(it.toString().toByteArray())
|
||||
bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
|
||||
}}",
|
||||
"loadorder": [${ModMgr.loadOrder.map { "\"${it}\"" }.joinToString()}],
|
||||
"worlds": [${ingame.gameworldIndices.joinToString()}]
|
||||
@@ -68,7 +68,7 @@ open class WriteMeta(val ingame: TerrarumIngame) {
|
||||
|
||||
fun encodeToByteArray64(): ByteArray64 {
|
||||
val ba = ByteArray64()
|
||||
this.invoke().toByteArray().forEach { ba.add(it) }
|
||||
this.invoke().toByteArray(Common.CHARSET).forEach { ba.add(it) }
|
||||
return ba
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,12 @@ open class WriteWorld(val ingame: TerrarumIngame) {
|
||||
world.genver = Common.GENVER
|
||||
world.comp = Common.COMP_GZIP
|
||||
|
||||
val baw = ByteArray64Writer()
|
||||
val baw = ByteArray64Writer(Common.CHARSET)
|
||||
|
||||
Common.jsoner.toJson(world, baw)
|
||||
baw.flush(); baw.close()
|
||||
|
||||
return baw.toByteArray64()
|
||||
|
||||
|
||||
/*val ba = ByteArray64()
|
||||
this.invoke().toByteArray().forEach { ba.add(it) }
|
||||
return ba*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
28
work_files/utftest-roundtrip.txt
Normal file
28
work_files/utftest-roundtrip.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
하수⬇️🏞는 두 산🏔🏞🏔 틈에서 나와 돌과 부딪쳐 싸우며🤜🏿🪨🤛🏻, 그 놀란 파도😲🌊와 성난 물머리와 우는 여울과 노한 물결과 슬픈 곡조와 원망하는 소리가 굽이쳐 돌면서, 우는 듯, 소리치는 듯, 바쁘게 호령하는 듯, 항상 장성을 깨뜨릴 형세가 있어, 전차 만승과 전기 만대나 전포 만가와 전고 만좌로써는 그 무너뜨리고 내뿜는 소리를 족히 형용할 수 없을 것이다. 모래 위에 큰 돌은 홀연히 떨어져 섰고, 강 언덕에 버드나무는 어둡고 컴컴하여 물지킴과 하수 귀신이 다투어 나와서 사람을 놀리는 듯한데, 좌우의 교리가 붙들려고 애쓰는 듯싶었다.
|
||||
혹은 말하기를,
|
||||
“여기는 옛 전쟁터이므로 강물이 저같이 우는 것이다”
|
||||
하지만 이는 그런 것이 아니니, 강물 소리는 듣기 여하에 달렸을 것이다.
|
||||
|
||||
河出兩山間,
|
||||
觸石鬪狼,
|
||||
其驚濤駭浪,
|
||||
憤瀾怒波,
|
||||
哀湍怨瀨,
|
||||
犇衝卷倒,
|
||||
嘶哮號喊,
|
||||
常有摧破長城之勢.
|
||||
戰車萬乘,
|
||||
戰騎萬隊,
|
||||
戰砲萬架,
|
||||
戰鼓萬坐,
|
||||
未足喩其崩塌潰壓之聲.
|
||||
沙上巨石,
|
||||
屹然離立,
|
||||
河堤柳樹,
|
||||
窅冥鴻蒙,
|
||||
如水祗河神爭出驕人,
|
||||
而左右蛟螭試其拏攫也.
|
||||
或曰: 「此古戰場,
|
||||
故河鳴然也」.
|
||||
此非爲其然也,
|
||||
河聲在聽之如何爾.
|
||||
28
work_files/utftest.txt
Normal file
28
work_files/utftest.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
하수⬇️🏞는 두 산🏔🏞🏔 틈에서 나와 돌과 부딪쳐 싸우며🤜🏿🪨🤛🏻, 그 놀란 파도😲🌊와 성난 물머리와 우는 여울과 노한 물결과 슬픈 곡조와 원망하는 소리가 굽이쳐 돌면서, 우는 듯, 소리치는 듯, 바쁘게 호령하는 듯, 항상 장성을 깨뜨릴 형세가 있어, 전차 만승과 전기 만대나 전포 만가와 전고 만좌로써는 그 무너뜨리고 내뿜는 소리를 족히 형용할 수 없을 것이다. 모래 위에 큰 돌은 홀연히 떨어져 섰고, 강 언덕에 버드나무는 어둡고 컴컴하여 물지킴과 하수 귀신이 다투어 나와서 사람을 놀리는 듯한데, 좌우의 교리가 붙들려고 애쓰는 듯싶었다.
|
||||
혹은 말하기를,
|
||||
“여기는 옛 전쟁터이므로 강물이 저같이 우는 것이다”
|
||||
하지만 이는 그런 것이 아니니, 강물 소리는 듣기 여하에 달렸을 것이다.
|
||||
|
||||
河出兩山間,
|
||||
觸石鬪狼,
|
||||
其驚濤駭浪,
|
||||
憤瀾怒波,
|
||||
哀湍怨瀨,
|
||||
犇衝卷倒,
|
||||
嘶哮號喊,
|
||||
常有摧破長城之勢.
|
||||
戰車萬乘,
|
||||
戰騎萬隊,
|
||||
戰砲萬架,
|
||||
戰鼓萬坐,
|
||||
未足喩其崩塌潰壓之聲.
|
||||
沙上巨石,
|
||||
屹然離立,
|
||||
河堤柳樹,
|
||||
窅冥鴻蒙,
|
||||
如水祗河神爭出驕人,
|
||||
而左右蛟螭試其拏攫也.
|
||||
或曰: 「此古戰場,
|
||||
故河鳴然也」.
|
||||
此非爲其然也,
|
||||
河聲在聽之如何爾.
|
||||
Reference in New Issue
Block a user