mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
testing the GDX's LZMA de/compressor
This commit is contained in:
@@ -4,7 +4,7 @@ package net.torvald.terrarum.gameworld
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataZip
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataLzma
|
||||
import org.dyn4j.geometry.Vector2
|
||||
|
||||
typealias BlockAddress = Long
|
||||
@@ -73,7 +73,7 @@ open class GameWorld {
|
||||
//layerAirPressure = MapLayerHalfFloat(width / 4, height / 8, 13f) // 1013 mBar
|
||||
}
|
||||
|
||||
internal constructor(worldIndex: Int, layerData: ReadLayerDataZip.LayerData) {
|
||||
internal constructor(worldIndex: Int, layerData: ReadLayerDataLzma.LayerData) {
|
||||
this.worldIndex = worldIndex
|
||||
|
||||
layerTerrain = layerData.layerTerrain
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.torvald.terrarum.modulebasegame.console
|
||||
import net.torvald.terrarum.console.ConsoleCommand
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.console.EchoError
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataLzma
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip
|
||||
|
||||
/**
|
||||
@@ -11,7 +12,7 @@ import net.torvald.terrarum.serialise.WriteLayerDataZip
|
||||
object ExportLayerData : ConsoleCommand {
|
||||
override fun execute(args: Array<String>) {
|
||||
try {
|
||||
val outfile = WriteLayerDataZip()
|
||||
val outfile = WriteLayerDataLzma()
|
||||
Echo("Layer data exported to ${outfile!!.canonicalPath}")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
|
||||
@@ -3,9 +3,8 @@ package net.torvald.terrarum.modulebasegame.console
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.console.ConsoleCommand
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.modulebasegame.IngameRenderer
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataZip
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataLzma
|
||||
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
|
||||
import java.io.File
|
||||
|
||||
@@ -20,7 +19,7 @@ object ImportLayerData : ConsoleCommand {
|
||||
}
|
||||
|
||||
val file = File(args[1])
|
||||
val layerData = ReadLayerDataZip(file)
|
||||
val layerData = ReadLayerDataLzma(file)
|
||||
|
||||
|
||||
Terrarum.ingame!!.world = GameWorldExtension(1, layerData)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameworld
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import net.torvald.terrarum.gameworld.*
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataZip
|
||||
import kotlin.properties.Delegates
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataLzma
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2018-07-03.
|
||||
@@ -11,7 +9,7 @@ import kotlin.properties.Delegates
|
||||
class GameWorldExtension: GameWorld {
|
||||
|
||||
constructor(worldIndex: Int, width: Int, height: Int) : super(worldIndex, width, height)
|
||||
internal constructor(worldIndex: Int, layerData: ReadLayerDataZip.LayerData) : super(worldIndex, layerData)
|
||||
internal constructor(worldIndex: Int, layerData: ReadLayerDataLzma.LayerData) : super(worldIndex, layerData)
|
||||
|
||||
|
||||
val time: WorldTime
|
||||
|
||||
@@ -8,9 +8,12 @@ import java.lang.IllegalArgumentException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Only being used by the title screen and the demoworld. This object may get deleted at any update
|
||||
*
|
||||
* Created by minjaesong on 2016-08-24.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
@Deprecated("TEMD is deprecated format; use TEMz which does compression")
|
||||
internal object ReadLayerData {
|
||||
|
||||
|
||||
@@ -112,4 +115,60 @@ internal object ReadLayerData {
|
||||
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.toLittle() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte()
|
||||
)
|
||||
fun Long.toLittle() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte(),
|
||||
this.ushr(32).and(0xFF).toByte(),
|
||||
this.ushr(40).and(0xFF).toByte(),
|
||||
this.ushr(48).and(0xFF).toByte(),
|
||||
this.ushr(56).and(0xFF).toByte()
|
||||
)
|
||||
fun Long.toLittle48() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte(),
|
||||
this.ushr(32).and(0xFF).toByte(),
|
||||
this.ushr(40).and(0xFF).toByte()
|
||||
)
|
||||
fun Double.toLittle() = java.lang.Double.doubleToRawLongBits(this).toLittle()
|
||||
fun Boolean.toLittle() = byteArrayOf(if (this) 0xFF.toByte() else 0.toByte())
|
||||
|
||||
fun ByteArray.toLittleInt() =
|
||||
if (this.size != 4) throw Error("Array not in size of 4")
|
||||
else this[0].toUint() or
|
||||
this[1].toUint().shl(8) or
|
||||
this[2].toUint().shl(16) or
|
||||
this[3].toUint().shl(24)
|
||||
fun ByteArray.toLittleLong() =
|
||||
if (this.size != 8) throw Error("Array not in size of 8")
|
||||
else this[0].toUlong() or
|
||||
this[1].toUlong().shl(8) or
|
||||
this[2].toUlong().shl(16) or
|
||||
this[3].toUlong().shl(24) or
|
||||
this[4].toUlong().shl(32) or
|
||||
this[5].toUlong().shl(40) or
|
||||
this[6].toUlong().shl(48) or
|
||||
this[7].toUlong().shl(56)
|
||||
fun ByteArray.toLittleInt48() =
|
||||
if (this.size != 6) throw Error("Array not in size of 6")
|
||||
else this[0].toUlong() or
|
||||
this[1].toUlong().shl(8) or
|
||||
this[2].toUlong().shl(16) or
|
||||
this[3].toUlong().shl(24) or
|
||||
this[4].toUlong().shl(32) or
|
||||
this[5].toUlong().shl(40)
|
||||
fun ByteArray.toLittleFloat() = java.lang.Float.intBitsToFloat(this.toLittleInt())
|
||||
|
||||
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||
277
src/net/torvald/terrarum/serialise/ReadLayerDataLzma.kt
Normal file
277
src/net/torvald/terrarum/serialise/ReadLayerDataLzma.kt
Normal file
@@ -0,0 +1,277 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.utils.compression.Lzma
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.gameworld.BlockAddress
|
||||
import net.torvald.terrarum.gameworld.BlockDamage
|
||||
import net.torvald.terrarum.gameworld.MapLayer
|
||||
import net.torvald.terrarum.gameworld.PairedMapLayer
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer.Companion.read
|
||||
import net.torvald.terrarum.toHex
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import kotlin.IllegalArgumentException
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-08-24.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object ReadLayerDataLzma {
|
||||
|
||||
// FIXME TERRAIN DAMAGE UNTESTED
|
||||
|
||||
internal operator fun invoke(file: File): LayerData {
|
||||
val inputStream = MarkableFileInputStream(FileInputStream(file))
|
||||
|
||||
|
||||
val magicBytes = ByteArray(4)
|
||||
|
||||
|
||||
//////////////////
|
||||
// FILE READING //
|
||||
//////////////////
|
||||
|
||||
|
||||
// read header first
|
||||
inputStream.read(magicBytes)
|
||||
if (!Arrays.equals(magicBytes, WriteLayerDataZip.MAGIC)) {
|
||||
throw IllegalArgumentException("File not a Layer Data")
|
||||
}
|
||||
|
||||
val versionNumber = inputStream.read(1)[0].toUint()
|
||||
val layerCount = inputStream.read(1)[0].toUint()
|
||||
val payloadCount = inputStream.read(1)[0].toUint()
|
||||
val compression = inputStream.read(1)[0].toUint()
|
||||
val width = inputStream.read(4).toLittleInt()
|
||||
val height = inputStream.read(4).toLittleInt()
|
||||
val spawnAddress = inputStream.read(6).toLittleInt48()
|
||||
|
||||
if (compression != 2) throw IllegalArgumentException("Input file is not compressed as LZMA; it's using algorithm $compression")
|
||||
|
||||
printdbg(this, "Version number: $versionNumber")
|
||||
printdbg(this, "Layers count: $layerCount")
|
||||
printdbg(this, "Payloads count: $payloadCount")
|
||||
printdbg(this, "Compression: $compression")
|
||||
printdbg(this, "Dimension: ${width}x$height")
|
||||
|
||||
// read payloads
|
||||
|
||||
val pldBuffer4 = ByteArray(4)
|
||||
val pldBuffer6 = ByteArray(6)
|
||||
val pldBuffer8 = ByteArray(8)
|
||||
|
||||
val payloads = HashMap<String, TEMzPayload>()
|
||||
|
||||
|
||||
// TODO please test the read; write has been fixed up
|
||||
|
||||
for (pldCnt in 0 until payloadCount) {
|
||||
inputStream.read(pldBuffer4)
|
||||
|
||||
// check payload header
|
||||
if (!pldBuffer4.contentEquals(WriteLayerDataZip.PAYLOAD_HEADER))
|
||||
throw InternalError("Payload $pldCnt not found -- expected ${WriteLayerDataZip.PAYLOAD_HEADER.toByteString()}, got ${pldBuffer4.toByteString()}")
|
||||
|
||||
// get payload's name
|
||||
inputStream.read(pldBuffer4)
|
||||
val payloadName = pldBuffer4.toString(Charset.forName("US-ASCII"))
|
||||
|
||||
printdbg(this, "Payload $pldCnt name: $payloadName") // maybe maybe related with buffer things?
|
||||
|
||||
// get uncompressed size
|
||||
inputStream.read(pldBuffer6)
|
||||
val uncompressedSize = pldBuffer6.toLittleInt48()
|
||||
|
||||
// get deflated size
|
||||
inputStream.mark(2147483647) // FIXME deflated stream cannot be larger than 2 GB
|
||||
// creep forward until we hit the PAYLOAD_FOOTER
|
||||
var deflatedSize: Int = 0 // FIXME deflated stream cannot be larger than 2 GB
|
||||
// loop init
|
||||
inputStream.read(pldBuffer8)
|
||||
// loop main
|
||||
while (!pldBuffer8.contentEquals(WriteLayerDataZip.PAYLOAD_FOOTER)) {
|
||||
val aByte = inputStream.read(); deflatedSize += 1
|
||||
if (aByte == -1) throw InternalError("Unexpected end-of-file at payload $pldCnt")
|
||||
pldBuffer8.shiftLeftBy(1, aByte.toByte())
|
||||
}
|
||||
|
||||
// at this point, we should have correct size of deflated bytestream
|
||||
|
||||
printdbg(this, "Payload $pldCnt compressed size: $deflatedSize")
|
||||
|
||||
val deflatedBytes = ByteArray(deflatedSize) // FIXME deflated stream cannot be larger than 2 GB
|
||||
inputStream.reset() // go back to marked spot
|
||||
inputStream.read(deflatedBytes)
|
||||
|
||||
// PRO Debug tip: every deflated bytes must begin with 0x789C or 0x78DA
|
||||
// Thus, \0pLd + [10] must be either of these.
|
||||
|
||||
// put constructed payload into a container
|
||||
payloads.put(payloadName, TEMzPayload(uncompressedSize, deflatedBytes))
|
||||
|
||||
// skip over to be aligned with the next payload
|
||||
inputStream.skip(8)
|
||||
}
|
||||
|
||||
|
||||
// test for EOF
|
||||
inputStream.read(pldBuffer8)
|
||||
if (!pldBuffer8.contentEquals(WriteLayerDataZip.FILE_FOOTER))
|
||||
throw InternalError("Expected end-of-file, got not-so-end-of-file")
|
||||
|
||||
|
||||
//////////////////////
|
||||
// END OF FILE READ //
|
||||
//////////////////////
|
||||
|
||||
val worldSize = width.toLong() * height
|
||||
|
||||
val payloadBytes = HashMap<String, ByteArray>()
|
||||
|
||||
payloads.forEach { t, u ->
|
||||
val inflatedOS = ByteArrayOutputStream(u.uncompressedSize.toInt()) // FIXME deflated stream cannot be larger than 2 GB
|
||||
|
||||
try {
|
||||
Lzma.decompress(ByteArrayInputStream(u.bytes), inflatedOS)
|
||||
}
|
||||
catch (e: RuntimeException) {
|
||||
// keep it empty (zero-sized file was compressed)
|
||||
}
|
||||
|
||||
val inflatedFile = inflatedOS.toByteArray()
|
||||
|
||||
// deal with (MSB ++ LSB)
|
||||
if (t == "TERR" || t == "WALL") {
|
||||
payloadBytes["${t}_MSB"] = inflatedFile.sliceArray(0 until worldSize.toInt()) // FIXME deflated stream cannot be larger than 2 GB
|
||||
payloadBytes["${t}_LSB"] = inflatedFile.sliceArray(worldSize.toInt() until u.uncompressedSize.toInt()) // FIXME deflated stream cannot be larger than 2 GB
|
||||
}
|
||||
else {
|
||||
payloadBytes[t] = inflatedFile
|
||||
}
|
||||
}
|
||||
|
||||
val spawnPoint = LandUtil.resolveBlockAddr(width, spawnAddress)
|
||||
|
||||
val terrainDamages = HashMap<BlockAddress, BlockDamage>()
|
||||
val wallDamages = HashMap<BlockAddress, BlockDamage>()
|
||||
|
||||
// parse terrain damages
|
||||
for (c in 0 until payloadBytes["TdMG"]!!.size step 10) {
|
||||
val bytes = payloadBytes["TdMG"]!!
|
||||
|
||||
val tileAddr = bytes.sliceArray(c..c+5)
|
||||
val value = bytes.sliceArray(c+6..c+9)
|
||||
|
||||
terrainDamages[tileAddr.toLittleInt48()] = value.toLittleFloat()
|
||||
}
|
||||
|
||||
|
||||
// parse wall damages
|
||||
for (c in 0 until payloadBytes["WdMG"]!!.size step 10) {
|
||||
val bytes = payloadBytes["WdMG"]!!
|
||||
|
||||
val tileAddr = bytes.sliceArray(c..c+5)
|
||||
val value = bytes.sliceArray(c+6..c+9)
|
||||
|
||||
wallDamages[tileAddr.toLittleInt48()] = value.toLittleFloat()
|
||||
}
|
||||
|
||||
|
||||
return LayerData(
|
||||
MapLayer(width, height, payloadBytes["WALL_MSB"]!!),
|
||||
MapLayer(width, height, payloadBytes["TERR_MSB"]!!),
|
||||
MapLayer(width, height, payloadBytes["WIRE"]!!),
|
||||
PairedMapLayer(width, height, payloadBytes["WALL_LSB"]!!),
|
||||
PairedMapLayer(width, height, payloadBytes["TERR_LSB"]!!),
|
||||
|
||||
spawnPoint.first, spawnPoint.second,
|
||||
|
||||
wallDamages, terrainDamages
|
||||
)
|
||||
}
|
||||
|
||||
private data class TEMzPayload(val uncompressedSize: Long, val bytes: ByteArray) // FIXME deflated stream cannot be larger than 2 GB
|
||||
|
||||
/**
|
||||
* Immediately deployable, a part of the gameworld
|
||||
*/
|
||||
internal data class LayerData(
|
||||
val layerWall: MapLayer,
|
||||
val layerTerrain: MapLayer,
|
||||
val layerWire: MapLayer,
|
||||
val layerWallLowBits: PairedMapLayer,
|
||||
val layerTerrainLowBits: PairedMapLayer,
|
||||
//val layerThermal: MapLayerHalfFloat, // in Kelvins
|
||||
//val layerAirPressure: MapLayerHalfFloat, // (milibar - 1000)
|
||||
|
||||
val spawnX: Int,
|
||||
val spawnY: Int,
|
||||
val wallDamages: HashMap<BlockAddress, BlockDamage>,
|
||||
val terrainDamages: HashMap<BlockAddress, BlockDamage>
|
||||
)
|
||||
|
||||
private fun ByteArray.shiftLeftBy(size: Int, fill: Byte = 0.toByte()) {
|
||||
if (size == 0) {
|
||||
return
|
||||
}
|
||||
else if (size < 0) {
|
||||
throw IllegalArgumentException("This won't shift to right (size = $size)")
|
||||
}
|
||||
else if (size >= this.size) {
|
||||
Arrays.fill(this, 0.toByte())
|
||||
}
|
||||
else {
|
||||
for (c in size..this.lastIndex) {
|
||||
this[c - size] = this[c]
|
||||
}
|
||||
for (c in (this.size - size)..this.lastIndex) {
|
||||
this[c] = fill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun InputStream.readRelative(b: ByteArray, off: Int, len: Int): Int {
|
||||
if (b == null) {
|
||||
throw NullPointerException()
|
||||
} else if (off < 0 || len < 0 || len > b.size) {
|
||||
throw IndexOutOfBoundsException()
|
||||
} else if (len == 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var c = read()
|
||||
if (c == -1) {
|
||||
return -1
|
||||
}
|
||||
b[0] = c.toByte()
|
||||
|
||||
var i = 1
|
||||
try {
|
||||
while (i < len) {
|
||||
c = read()
|
||||
if (c == -1) {
|
||||
break
|
||||
}
|
||||
b[i] = c.toByte()
|
||||
i++
|
||||
}
|
||||
} catch (ee: IOException) {
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
fun ByteArray.toByteString(): String {
|
||||
val sb = StringBuilder()
|
||||
this.forEach {
|
||||
sb.append(it.toUint().toHex().takeLast(2))
|
||||
sb.append(' ')
|
||||
}
|
||||
sb.deleteCharAt(sb.lastIndex)
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import kotlin.collections.HashMap
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object ReadLayerDataZip {
|
||||
|
||||
// FIXME UNTESTED !!
|
||||
// FIXME TERRAIN DAMAGE UNTESTED
|
||||
|
||||
internal operator fun invoke(file: File): LayerData {
|
||||
val inputStream = MarkableFileInputStream(FileInputStream(file))
|
||||
@@ -49,6 +49,8 @@ internal object ReadLayerDataZip {
|
||||
val height = inputStream.read(4).toLittleInt()
|
||||
val spawnAddress = inputStream.read(6).toLittleInt48()
|
||||
|
||||
if (compression != 1) throw IllegalArgumentException("Input file is not compressed as DEFLATE; it's using algorithm $compression")
|
||||
|
||||
printdbg(this, "Version number: $versionNumber")
|
||||
printdbg(this, "Layers count: $layerCount")
|
||||
printdbg(this, "Payloads count: $payloadCount")
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.zip.GZIPOutputStream
|
||||
* Created by minjaesong on 2016-03-18.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
@Deprecated("TEMD is deprecated format; use TEMz which does compression")
|
||||
internal object WriteLayerData {
|
||||
|
||||
val LAYERS_FILENAME = "worldinfo1"
|
||||
@@ -86,43 +87,3 @@ internal object WriteLayerData {
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*fun Int.toLittle() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte()
|
||||
)
|
||||
fun Long.toLittle() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte(),
|
||||
this.ushr(32).and(0xFF).toByte(),
|
||||
this.ushr(40).and(0xFF).toByte(),
|
||||
this.ushr(48).and(0xFF).toByte(),
|
||||
this.ushr(56).and(0xFF).toByte()
|
||||
)
|
||||
fun Double.toLittle() = java.lang.Double.doubleToRawLongBits(this).toLittle()
|
||||
fun Boolean.toLittle() = byteArrayOf(if (this) 0xFF.toByte() else 0.toByte())
|
||||
|
||||
fun ByteArray.toLittleInt() =
|
||||
if (this.size != 4) throw Error("Array not in size of 4")
|
||||
else this[0].toUint() or
|
||||
this[1].toUint().shl(8) or
|
||||
this[2].toUint().shl(16) or
|
||||
this[3].toUint().shl(24)
|
||||
fun ByteArray.toLittleLong() =
|
||||
if (this.size != 8) throw Error("Array not in size of 8")
|
||||
else this[0].toUlong() or
|
||||
this[1].toUlong().shl(8) or
|
||||
this[2].toUlong().shl(16) or
|
||||
this[3].toUlong().shl(24) or
|
||||
this[4].toUlong().shl(32) or
|
||||
this[5].toUlong().shl(40) or
|
||||
this[6].toUlong().shl(48) or
|
||||
this[7].toUlong().shl(56)
|
||||
fun ByteArray.toLittleDouble() = java.lang.Double.longBitsToDouble(this.toLittleLong())
|
||||
|
||||
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)*/
|
||||
175
src/net/torvald/terrarum/serialise/WriteLayerDataLzma.kt
Normal file
175
src/net/torvald/terrarum/serialise/WriteLayerDataLzma.kt
Normal file
@@ -0,0 +1,175 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.utils.compression.Lzma
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.console.EchoError
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
|
||||
/**
|
||||
* This object only writes a file named 'worldinfo1'.
|
||||
*
|
||||
* The intended operation is as follows:
|
||||
* 1. This and others write
|
||||
*
|
||||
* TODO temporarily dump on the disk THEN pack? Or put all the files (in ByteArray64) in the RAM THEN pack?
|
||||
*
|
||||
* Created by minjaesong on 2016-03-18.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object WriteLayerDataLzma {
|
||||
|
||||
// FIXME TERRAIN DAMAGE UNTESTED
|
||||
|
||||
|
||||
// 2400x800 world size: about .. kB
|
||||
// 8192x2048 world size: about 470 kB but writes much slower than DEFLATE
|
||||
|
||||
|
||||
val LAYERS_FILENAME = "world"
|
||||
|
||||
val MAGIC = byteArrayOf(0x54, 0x45, 0x4D, 0x7A)
|
||||
val VERSION_NUMBER = 3.toByte()
|
||||
val NUMBER_OF_LAYERS = 3.toByte()
|
||||
val NUMBER_OF_PAYLOADS = 5.toByte()
|
||||
val COMPRESSION_ALGORITHM = 2.toByte()
|
||||
val PAYLOAD_HEADER = byteArrayOf(0, 0x70, 0x4C, 0x64)
|
||||
val PAYLOAD_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x50, 0x59, 0x4C, 0x64, -1)
|
||||
val FILE_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x54, 0x45, 0x4D, -1, -2)
|
||||
|
||||
//val NULL: Byte = 0
|
||||
|
||||
|
||||
/**
|
||||
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
|
||||
*
|
||||
* @return File on success; `null` on failure
|
||||
*/
|
||||
internal operator fun invoke(): File? {
|
||||
val world = (Terrarum.ingame!!.world)
|
||||
|
||||
val path = "${Terrarum.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
|
||||
|
||||
// TODO let's try dump-on-the-disk-then-pack method...
|
||||
|
||||
/*val parentDir = File("${Terrarum.defaultSaveDir}/$saveDirectoryName")
|
||||
if (!parentDir.exists()) {
|
||||
parentDir.mkdir()
|
||||
}
|
||||
else if (!parentDir.isDirectory) {
|
||||
EchoError("Savegame directory is not actually a directory, aborting...")
|
||||
return false
|
||||
}*/
|
||||
|
||||
|
||||
val outFile = File(path)
|
||||
if (outFile.exists()) outFile.delete()
|
||||
outFile.createNewFile()
|
||||
|
||||
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 8192)
|
||||
var deflater: DeflaterOutputStream // couldn't really use one outputstream for all the files.
|
||||
|
||||
fun wb(byteArray: ByteArray) { outputStream.write(byteArray) }
|
||||
fun wb(byte: Byte) { outputStream.write(byte.toInt()) }
|
||||
//fun wb(byte: Int) { outputStream.write(byte) }
|
||||
fun wi32(int: Int) { wb(int.toLittle()) }
|
||||
fun wi48(long: Long) { wb(long.toLittle48()) }
|
||||
fun wi64(long: Long) { wb(long.toLittle()) }
|
||||
fun wf32(float: Float) { wi32(float.toRawBits()) }
|
||||
|
||||
|
||||
////////////////////
|
||||
// WRITE BINARIES //
|
||||
////////////////////
|
||||
|
||||
|
||||
// all the necessary headers
|
||||
wb(MAGIC); wb(VERSION_NUMBER); wb(NUMBER_OF_LAYERS); wb(NUMBER_OF_PAYLOADS); wb(COMPRESSION_ALGORITHM)
|
||||
|
||||
// world width, height, and spawn point
|
||||
wi32(world.width); wi32(world.height)
|
||||
wi48(LandUtil.getBlockAddr(world, world.spawnX, world.spawnY))
|
||||
|
||||
// write payloads //
|
||||
outputStream.flush()
|
||||
|
||||
// TERR payload
|
||||
// PRO Debug tip: every deflated bytes must begin with 0x789C or 0x78DA
|
||||
// Thus, \0pLd + [10] must be either of these.
|
||||
|
||||
wb(PAYLOAD_HEADER); wb("TERR".toByteArray())
|
||||
wi48(world.width * world.height * 3L / 2)
|
||||
Lzma.compress(ByteArrayInputStream(world.terrainArray), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(world.layerTerrainLowBits.data), outputStream)
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WALL payload
|
||||
wb(PAYLOAD_HEADER); wb("WALL".toByteArray())
|
||||
wi48(world.width * world.height * 3L / 2)
|
||||
Lzma.compress(ByteArrayInputStream(world.wallArray), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(world.layerWallLowBits.data), outputStream)
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WIRE payload
|
||||
wb(PAYLOAD_HEADER); wb("WIRE".toByteArray())
|
||||
wi48(world.width * world.height.toLong())
|
||||
Lzma.compress(ByteArrayInputStream(world.wireArray), outputStream)
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// TdMG payload
|
||||
wb(PAYLOAD_HEADER); wb("TdMG".toByteArray())
|
||||
wi48(world.terrainDamages.size.toLong())
|
||||
|
||||
|
||||
world.terrainDamages.forEach { t, u ->
|
||||
Lzma.compress(ByteArrayInputStream(t.toLittle48()), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(u.toRawBits().toLittle()), outputStream)
|
||||
}
|
||||
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WdMG payload
|
||||
wb(PAYLOAD_HEADER); wb("WdMG".toByteArray())
|
||||
wi48(world.wallDamages.size.toLong())
|
||||
|
||||
|
||||
world.wallDamages.forEach { t, u ->
|
||||
Lzma.compress(ByteArrayInputStream(t.toLittle48()), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(u.toRawBits().toLittle()), outputStream)
|
||||
}
|
||||
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// write footer
|
||||
wb(FILE_FOOTER)
|
||||
|
||||
|
||||
//////////////////
|
||||
// END OF WRITE //
|
||||
//////////////////
|
||||
|
||||
|
||||
|
||||
// replace savemeta with tempfile
|
||||
try {
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
|
||||
return outFile
|
||||
}
|
||||
catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
finally {
|
||||
outputStream.close()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import java.util.zip.GZIPOutputStream
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object WriteLayerDataZip {
|
||||
|
||||
// FIXME output seems legit, but I can't confirm right now !!
|
||||
// FIXME TERRAIN DAMAGE UNTESTED
|
||||
|
||||
|
||||
// 2400x800 world size, default comp level: about 90 kB
|
||||
@@ -189,59 +189,3 @@ internal object WriteLayerDataZip {
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun Int.toLittle() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte()
|
||||
)
|
||||
fun Long.toLittle() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte(),
|
||||
this.ushr(32).and(0xFF).toByte(),
|
||||
this.ushr(40).and(0xFF).toByte(),
|
||||
this.ushr(48).and(0xFF).toByte(),
|
||||
this.ushr(56).and(0xFF).toByte()
|
||||
)
|
||||
fun Long.toLittle48() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte(),
|
||||
this.ushr(16).and(0xFF).toByte(),
|
||||
this.ushr(24).and(0xFF).toByte(),
|
||||
this.ushr(32).and(0xFF).toByte(),
|
||||
this.ushr(40).and(0xFF).toByte()
|
||||
)
|
||||
fun Double.toLittle() = java.lang.Double.doubleToRawLongBits(this).toLittle()
|
||||
fun Boolean.toLittle() = byteArrayOf(if (this) 0xFF.toByte() else 0.toByte())
|
||||
|
||||
fun ByteArray.toLittleInt() =
|
||||
if (this.size != 4) throw Error("Array not in size of 4")
|
||||
else this[0].toUint() or
|
||||
this[1].toUint().shl(8) or
|
||||
this[2].toUint().shl(16) or
|
||||
this[3].toUint().shl(24)
|
||||
fun ByteArray.toLittleLong() =
|
||||
if (this.size != 8) throw Error("Array not in size of 8")
|
||||
else this[0].toUlong() or
|
||||
this[1].toUlong().shl(8) or
|
||||
this[2].toUlong().shl(16) or
|
||||
this[3].toUlong().shl(24) or
|
||||
this[4].toUlong().shl(32) or
|
||||
this[5].toUlong().shl(40) or
|
||||
this[6].toUlong().shl(48) or
|
||||
this[7].toUlong().shl(56)
|
||||
fun ByteArray.toLittleInt48() =
|
||||
if (this.size != 6) throw Error("Array not in size of 6")
|
||||
else this[0].toUlong() or
|
||||
this[1].toUlong().shl(8) or
|
||||
this[2].toUlong().shl(16) or
|
||||
this[3].toUlong().shl(24) or
|
||||
this[4].toUlong().shl(32) or
|
||||
this[5].toUlong().shl(40)
|
||||
fun ByteArray.toLittleFloat() = java.lang.Float.intBitsToFloat(this.toLittleInt())
|
||||
|
||||
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||
@@ -14,7 +14,7 @@ Ord Hex Description
|
||||
|
||||
06 05 Number of payloads
|
||||
|
||||
07 01 Compression algorithm, 0 for none, 1 for DEFLATE, otherwise undefined (maybe LZMA2 for the future?)
|
||||
07 01 Compression algorithm, 0 for none, 1 for DEFLATE, 2 for LZMA, otherwise undefined (maybe LZMA2 for the future?)
|
||||
|
||||
08 World width
|
||||
09 World width
|
||||
|
||||
Reference in New Issue
Block a user