mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-14 12:34:05 +09:00
completely abolishing GSON; new save format impl wip
This commit is contained in:
@@ -1,114 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip.FILE_FOOTER
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip.PAYLOAD_FOOTER
|
||||
import net.torvald.terrarum.toHex
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2019-02-20.
|
||||
*/
|
||||
|
||||
object PayloadUtil {
|
||||
/**
|
||||
* InputStream must be located manually at the payload begin
|
||||
*
|
||||
* For the actual use case, take a look at the source of the [ReadLayerDataZip].
|
||||
*/
|
||||
fun readAll(inputStream: MarkableFileInputStream, footer: ByteArray = FILE_FOOTER): HashMap<String, TEMzPayload> {
|
||||
val pldBuffer4 = ByteArray(4)
|
||||
val pldBuffer6 = ByteArray(6)
|
||||
val pldBuffer8 = ByteArray(8)
|
||||
|
||||
val payloads = HashMap<String, TEMzPayload>()
|
||||
|
||||
var pldCnt = 1
|
||||
|
||||
while (true) {
|
||||
// read header and get payload's name
|
||||
inputStream.read(pldBuffer8)
|
||||
|
||||
// check if end of payload reached
|
||||
if (pldBuffer8.contentEquals(footer)) {
|
||||
break
|
||||
}
|
||||
|
||||
val payloadName = pldBuffer8.copyOfRange(4, 8).toString(Charset.forName("US-ASCII"))
|
||||
|
||||
AppLoader.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 compressedSize: Int = 0 // FIXME deflated stream cannot be larger than 2 GB
|
||||
// loop init
|
||||
inputStream.read(pldBuffer8)
|
||||
// loop main
|
||||
while (!pldBuffer8.contentEquals(PAYLOAD_FOOTER)) {
|
||||
val aByte = inputStream.read(); compressedSize += 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
|
||||
|
||||
AppLoader.printdbg(this, "Payload $pldCnt compressed size: $compressedSize")
|
||||
|
||||
val compressedBytes = ByteArray(compressedSize) // FIXME deflated stream cannot be larger than 2 GB
|
||||
inputStream.reset() // go back to marked spot
|
||||
inputStream.read(compressedBytes)
|
||||
|
||||
// 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, compressedBytes))
|
||||
|
||||
// skip over to be aligned with the next payload
|
||||
inputStream.skip(8)
|
||||
|
||||
pldCnt += 1
|
||||
}
|
||||
|
||||
return payloads
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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()
|
||||
}
|
||||
|
||||
data class TEMzPayload(val uncompressedSize: Long, val bytes: ByteArray) // FIXME deflated stream cannot be larger than 2 GB
|
||||
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
init {
|
||||
throw Error("TEMD is old and removed format; use TEMz which does compression")
|
||||
}
|
||||
|
||||
/*internal operator fun invoke(inputStream: InputStream, inWorld: GameWorldExtension? = null): GameWorldExtension {
|
||||
val magicBytes = ByteArray(4)
|
||||
val layerSizeBytes = ByteArray(1)
|
||||
val layerCountBytes = ByteArray(1)
|
||||
val worldWidthBytes = ByteArray(4)
|
||||
val worldHeightBytes = ByteArray(4)
|
||||
val spawnCoordXBytes = ByteArray(4)
|
||||
val spawnCoordYBytes = ByteArray(4)
|
||||
|
||||
// read header first
|
||||
inputStream.read(magicBytes)
|
||||
if (!Arrays.equals(magicBytes, WriteLayerData.MAGIC)) {
|
||||
throw IllegalArgumentException("File not a Layer Data")
|
||||
}
|
||||
|
||||
inputStream.read(layerSizeBytes)
|
||||
inputStream.read(layerCountBytes)
|
||||
inputStream.skip(2) // reserved bytes
|
||||
inputStream.read(worldWidthBytes)
|
||||
inputStream.read(worldHeightBytes)
|
||||
inputStream.read(spawnCoordXBytes)
|
||||
inputStream.read(spawnCoordYBytes)
|
||||
|
||||
val worldWidth = worldWidthBytes.toLittleInt()
|
||||
val worldHeight = worldHeightBytes.toLittleInt()
|
||||
val bytesPerTile = layerSizeBytes[0].toUint()
|
||||
val layerCount = layerCountBytes[0].toUint()
|
||||
val layerSize = worldWidth * worldHeight * bytesPerTile
|
||||
|
||||
val terrainLayerMSB = ByteArray(layerSize)
|
||||
val wallLayerMSB = ByteArray(layerSize)
|
||||
val terrainLayerLSB = ByteArray(layerSize / 2)
|
||||
val wallLayerLSB = ByteArray(layerSize / 2)
|
||||
var wireLayer: ByteArray? = null
|
||||
|
||||
inputStream.read(terrainLayerMSB)
|
||||
inputStream.read(wallLayerMSB)
|
||||
inputStream.read(terrainLayerLSB)
|
||||
inputStream.read(wallLayerLSB)
|
||||
|
||||
if (layerCount == 4) {
|
||||
wireLayer = ByteArray(layerSize)
|
||||
inputStream.read(wireLayer)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// create world out of tiles data
|
||||
|
||||
val retWorld = inWorld ?: GameWorldExtension(1, worldWidth, worldHeight, 0, 0, 0) // FIXME null TIME_T for the (partial) test to pass
|
||||
|
||||
retWorld.layerTerrain.data = terrainLayerMSB
|
||||
retWorld.layerWall.data = wallLayerMSB
|
||||
retWorld.layerTerrainLowBits.data = terrainLayerLSB
|
||||
retWorld.layerWallLowBits.data = wallLayerLSB
|
||||
|
||||
|
||||
retWorld.spawnX = spawnCoordXBytes.toLittleInt()
|
||||
retWorld.spawnY = spawnCoordYBytes.toLittleInt()
|
||||
|
||||
|
||||
return retWorld
|
||||
}
|
||||
|
||||
|
||||
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 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()
|
||||
)
|
||||
/** Converts int as 2-byte array, discarding the sign.*/
|
||||
fun Int.toULittleShort() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte()
|
||||
)
|
||||
/** Converts int as 2-byte array, preserving the sign. In other words, it converts int to short. */
|
||||
fun Int.toLittleShort() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.shr(8).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()
|
||||
)
|
||||
/** Converts long as 6-byte array, discarding the sign. */
|
||||
fun Long.toULittle48() = 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.toULittleShort() =
|
||||
if (this.size != 4) throw Error("Array not in size of 2")
|
||||
else this[0].toUint() or
|
||||
this[1].toUint().shl(8)
|
||||
fun ByteArray.toLittleShort() =
|
||||
if (this.size != 4) throw Error("Array not in size of 2")
|
||||
else this[0].toUint() or
|
||||
this[1].toInt().shl(8)
|
||||
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)
|
||||
|
||||
const val WORLD_GENERATOR_VERSION = 1
|
||||
@@ -1,224 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.utils.compression.Lzma
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.gameitem.ItemID
|
||||
import net.torvald.terrarum.gameworld.BlockAddress
|
||||
import net.torvald.terrarum.gameworld.BlockLayer
|
||||
import net.torvald.terrarum.gameworld.FluidType
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer.Companion.read
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
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): ReadLayerDataZip.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 generatorVer = inputStream.read(2).toULittleShort()
|
||||
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, "World generator version: $generatorVer")
|
||||
printdbg(this, "Dimension: ${width}x$height")
|
||||
|
||||
// read payloads
|
||||
|
||||
val payloads = PayloadUtil.readAll(inputStream)
|
||||
/*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()
|
||||
|
||||
payloadBytes[t] = inflatedFile
|
||||
}
|
||||
|
||||
val spawnPoint = LandUtil.resolveBlockAddr(width, spawnAddress)
|
||||
|
||||
val terrainDamages = HashMap<BlockAddress, Float>()
|
||||
val wallDamages = HashMap<BlockAddress, Float>()
|
||||
val fluidTypes = HashMap<BlockAddress, FluidType>()
|
||||
val fluidFills = HashMap<BlockAddress, Float>()
|
||||
val tileNumToName = HashMap<Int, ItemID>()
|
||||
|
||||
// parse terrain damages
|
||||
for (c in payloadBytes["TdMG"]!!.indices 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 payloadBytes["WdMG"]!!.indices 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()
|
||||
}
|
||||
|
||||
|
||||
// TODO parse fluid(Types|Fills)
|
||||
|
||||
// TODO parse tileNumToName
|
||||
|
||||
|
||||
return ReadLayerDataZip.LayerData(
|
||||
BlockLayer(width, height, payloadBytes["WALL"]!!),
|
||||
BlockLayer(width, height, payloadBytes["TERR"]!!),
|
||||
|
||||
spawnPoint.first, spawnPoint.second,
|
||||
|
||||
wallDamages, terrainDamages, fluidTypes, fluidFills, tileNumToName
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.gameitem.ItemID
|
||||
import net.torvald.terrarum.gameworld.BlockAddress
|
||||
import net.torvald.terrarum.gameworld.BlockLayer
|
||||
import net.torvald.terrarum.gameworld.FluidType
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer.Companion.read
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.zip.Inflater
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-08-24.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object ReadLayerDataZip {
|
||||
|
||||
// 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 generatorVer = inputStream.read(2).toULittleShort()
|
||||
val width = inputStream.read(4).toLittleInt()
|
||||
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")
|
||||
printdbg(this, "Compression: $compression")
|
||||
printdbg(this, "World generator version: $generatorVer")
|
||||
printdbg(this, "Dimension: ${width}x$height")
|
||||
|
||||
// read payloads
|
||||
|
||||
val payloads = PayloadUtil.readAll(inputStream)
|
||||
/*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 inflatedFile = ByteArray(u.uncompressedSize.toInt()) // FIXME deflated stream cannot be larger than 2 GB
|
||||
val inflater = Inflater()
|
||||
inflater.setInput(u.bytes, 0, u.bytes.size)
|
||||
val uncompLen = inflater.inflate(inflatedFile)
|
||||
|
||||
// just in case
|
||||
if (uncompLen.toLong() != u.uncompressedSize)
|
||||
throw InternalError("Payload $t DEFLATE size mismatch -- expected ${u.uncompressedSize}, got $uncompLen")
|
||||
|
||||
payloadBytes[t] = inflatedFile
|
||||
}
|
||||
|
||||
val spawnPoint = LandUtil.resolveBlockAddr(width, spawnAddress)
|
||||
|
||||
val terrainDamages = HashMap<BlockAddress, Float>()
|
||||
val wallDamages = HashMap<BlockAddress, Float>()
|
||||
val fluidTypes = HashMap<BlockAddress, FluidType>()
|
||||
val fluidFills = HashMap<BlockAddress, Float>()
|
||||
val tileNumberToNameMap = HashMap<Int, ItemID>()
|
||||
|
||||
// parse terrain damages
|
||||
for (c in payloadBytes["TdMG"]!!.indices 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 payloadBytes["WdMG"]!!.indices 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()
|
||||
}
|
||||
|
||||
|
||||
// TODO parse fluid(Types|Fills)
|
||||
|
||||
|
||||
return LayerData(
|
||||
BlockLayer(width, height, payloadBytes["WALL"]!!),
|
||||
BlockLayer(width, height, payloadBytes["TERR"]!!),
|
||||
|
||||
spawnPoint.first, spawnPoint.second,
|
||||
|
||||
wallDamages, terrainDamages, fluidTypes, fluidFills, tileNumberToNameMap
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately deployable, a part of the gameworld
|
||||
*/
|
||||
internal data class LayerData(
|
||||
val layerWall: BlockLayer,
|
||||
val layerTerrain: BlockLayer,
|
||||
//val layerThermal: MapLayerHalfFloat, // in Kelvins
|
||||
//val layerAirPressure: MapLayerHalfFloat, // (milibar - 1000)
|
||||
|
||||
val spawnX: Int,
|
||||
val spawnY: Int,
|
||||
|
||||
//val wirings: HashMap<BlockAddress, SortedArrayList<GameWorld.WiringNode>>,
|
||||
val wallDamages: HashMap<BlockAddress, Float>,
|
||||
val terrainDamages: HashMap<BlockAddress, Float>,
|
||||
val fluidTypes: HashMap<BlockAddress, FluidType>,
|
||||
val fluidFills: HashMap<BlockAddress, Float>,
|
||||
val tileNumberToNameMap: HashMap<Int, ItemID>
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer.Companion.read
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.*
|
||||
|
||||
|
||||
object ReadWorldInfo {
|
||||
|
||||
// FIXME UNTESTED
|
||||
|
||||
internal operator fun invoke(file: File): SaveMetaData {
|
||||
|
||||
val magic = ByteArray(4)
|
||||
val worldNameUTF8 = ArrayList<Byte>()
|
||||
|
||||
val fis = FileInputStream(file)
|
||||
|
||||
fis.read(magic)
|
||||
if (!Arrays.equals(magic, WriteWorldInfo.META_MAGIC)) {
|
||||
throw IllegalArgumentException("File not a Save Meta")
|
||||
}
|
||||
|
||||
|
||||
val descVersion = fis.read(1) // 0-127
|
||||
val numberOfHashes = fis.read() // 0-127
|
||||
|
||||
|
||||
var byteRead = fis.read()
|
||||
while (byteRead != 0) {
|
||||
if (byteRead == -1)
|
||||
throw InternalError("Unexpected EOF")
|
||||
|
||||
worldNameUTF8.add(byteRead.toByte())
|
||||
byteRead = fis.read()
|
||||
}
|
||||
|
||||
return SaveMetaData(
|
||||
String(worldNameUTF8.toByteArray(), Charsets.UTF_8),
|
||||
fis.read(8).toLittleLong(), // terrain seed
|
||||
fis.read(8).toLittleLong(), // rng s0
|
||||
fis.read(8).toLittleLong(), // rng s1
|
||||
fis.read(8).toLittleLong(), // weather s0
|
||||
fis.read(8).toLittleLong(), // weather s1
|
||||
fis.read(4).toLittleInt(), // player id
|
||||
fis.read(8).toLittleLong(), // world TIME_T
|
||||
fis.read(6).toLittleLong(), // creation time
|
||||
fis.read(6).toLittleLong(), // last play time
|
||||
fis.read(4).toLittleInt(), // total time wasted
|
||||
fis.read(32), // sha256sum worldinfo1
|
||||
fis.read(32), // sha256sum worldinfo2
|
||||
fis.read(32) // sha256sum worldinfo3
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
data class SaveMetaData(
|
||||
val worldName: String,
|
||||
val terrainSeed: Long,
|
||||
val rngS0: Long,
|
||||
val rngS1: Long,
|
||||
val weatherS0: Long,
|
||||
val weatherS1: Long,
|
||||
val playerID: Int,
|
||||
val timeNow: Long,
|
||||
val creationTime: Long,
|
||||
val lastPlayTime: Long,
|
||||
val totalPlayTime: Int,
|
||||
val worldinfo1Hash: ByteArray,
|
||||
val worldInfo2Hash: ByteArray,
|
||||
val worldInfo3Hash: ByteArray,
|
||||
|
||||
// gzipped TGA in meta
|
||||
val thumbnail: Texture = Texture(2, 2, Pixmap.Format.RGBA8888),
|
||||
// skim through the virtualdisk entries
|
||||
val worldCount: Int = 1,
|
||||
// read from the entry file
|
||||
val playerName: String = "Savegame",
|
||||
val playerWallet: Int = 0
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package net.torvald.terrarum.serialise
|
||||
/**
|
||||
* Created by minjaesong on 2018-10-03.
|
||||
*/
|
||||
object SavegameLoader {
|
||||
object SavegameReader {
|
||||
|
||||
// TODO read TEVd, load necessary shits
|
||||
|
||||
@@ -9,7 +9,6 @@ import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.gameitem.GameItem
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
|
||||
import net.torvald.terrarum.utils.JsonWriter.getJsonBuilder
|
||||
import net.torvald.util.SortedArrayList
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
@@ -77,76 +76,17 @@ object SavegameWriter {
|
||||
|
||||
// serialise current world (stage)
|
||||
|
||||
val worldBytes = WriteLayerDataZip(gameworld) // filename can be anything that is "world[n]" where [n] is any number
|
||||
if (worldBytes == null) {
|
||||
throw Error("Serialising world failed")
|
||||
}
|
||||
|
||||
if (!worldBytes.sliceArray(0..3).contentEquals(WriteLayerDataZip.MAGIC)) {
|
||||
worldBytes.forEach {
|
||||
print(it.toUInt().and(255u).toString(16).toUpperCase().padStart(2, '0'))
|
||||
print(' ')
|
||||
}; println()
|
||||
throw Error()
|
||||
}
|
||||
|
||||
// add current world (stage) to the disk
|
||||
VDUtil.registerFile(disk, DiskEntry(
|
||||
gameworld.worldIndex, ROOT,
|
||||
"world${gameworld.worldIndex}".toByteArray(charset),
|
||||
creationDate, creationDate,
|
||||
EntryFile(worldBytes)
|
||||
))
|
||||
|
||||
|
||||
|
||||
// TODO world[n] is done, needs whole other things
|
||||
|
||||
|
||||
// worldinfo0..3
|
||||
val worldinfoBytes = WriteWorldInfo(ingame)
|
||||
worldinfoBytes?.forEachIndexed { index, bytes ->
|
||||
VDUtil.registerFile(disk, DiskEntry(
|
||||
32766 - index, ROOT, "worldinfo$index".toByteArray(charset),
|
||||
creationDate, creationDate,
|
||||
EntryFile(bytes)
|
||||
))
|
||||
} ?: throw Error("Serialising worldinfo failed")
|
||||
|
||||
// loadorder.txt
|
||||
VDUtil.registerFile(disk, DiskEntry(
|
||||
32767, ROOT, "load_order.txt".toByteArray(charset),
|
||||
creationDate, creationDate,
|
||||
EntryFile(ByteArray64.fromByteArray(Gdx.files.internal("./assets/mods/LoadOrder.csv").readBytes()))
|
||||
))
|
||||
|
||||
// actors
|
||||
ingame.actorContainerActive.forEach {
|
||||
VDUtil.registerFile(disk, DiskEntry(
|
||||
rngPool.next(), ROOT,
|
||||
it.referenceID.toString(16).toUpperCase().toByteArray(charset),
|
||||
creationDate, creationDate,
|
||||
EntryFile(serialiseActor(it))
|
||||
))
|
||||
}
|
||||
ingame.actorContainerInactive.forEach {
|
||||
VDUtil.registerFile(disk, DiskEntry(
|
||||
rngPool.next(), ROOT,
|
||||
it.referenceID.toString(16).toUpperCase().toByteArray(charset),
|
||||
creationDate, creationDate,
|
||||
EntryFile(serialiseActor(it))
|
||||
))
|
||||
}
|
||||
|
||||
// items
|
||||
ItemCodex.dynamicItemDescription.forEach { dynamicID, item ->
|
||||
/*ItemCodex.dynamicItemDescription.forEach { dynamicID, item ->
|
||||
VDUtil.registerFile(disk, DiskEntry(
|
||||
rngPool.next(), ROOT,
|
||||
dynamicID.toByteArray(charset),
|
||||
creationDate, creationDate,
|
||||
EntryFile(serialiseItem(item))
|
||||
))
|
||||
}
|
||||
}*/
|
||||
|
||||
System.gc()
|
||||
|
||||
@@ -156,14 +96,79 @@ object SavegameWriter {
|
||||
fun modifyExistingSave(savefile: File): VirtualDisk {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
private fun serialiseActor(a: Actor): ByteArray64 {
|
||||
val gson = getJsonBuilder().toJson(a).toByteArray(charset)
|
||||
return ByteArray64.fromByteArray(gson)
|
||||
}
|
||||
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()
|
||||
)
|
||||
/** Converts int as 2-byte array, discarding the sign.*/
|
||||
fun Int.toULittleShort() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.ushr(8).and(0xFF).toByte()
|
||||
)
|
||||
/** Converts int as 2-byte array, preserving the sign. In other words, it converts int to short. */
|
||||
fun Int.toLittleShort() = byteArrayOf(
|
||||
this.and(0xFF).toByte(),
|
||||
this.shr(8).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()
|
||||
)
|
||||
/** Converts long as 6-byte array, discarding the sign. */
|
||||
fun Long.toULittle48() = 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())
|
||||
|
||||
private fun serialiseItem(i: GameItem): ByteArray64 {
|
||||
val gson = getJsonBuilder().toJson(i).toByteArray(charset)
|
||||
return ByteArray64.fromByteArray(gson)
|
||||
}
|
||||
}
|
||||
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.toULittleShort() =
|
||||
if (this.size != 4) throw Error("Array not in size of 2")
|
||||
else this[0].toUint() or
|
||||
this[1].toUint().shl(8)
|
||||
fun ByteArray.toLittleShort() =
|
||||
if (this.size != 4) throw Error("Array not in size of 2")
|
||||
else this[0].toUint() or
|
||||
this[1].toInt().shl(8)
|
||||
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)
|
||||
@@ -1,61 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-18.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object WriteCSV {
|
||||
val META_FILENAME_TILE = "worldinfo2"
|
||||
val META_FILENAME_ITEM = "worldinfo3"
|
||||
val META_FILENAME_MAT = "worldinfo4"
|
||||
|
||||
internal fun write(saveDirectoryName: String): Boolean {
|
||||
//val tileCSV = CSVFetcher.readCSVasString(BlockCodex.CSV_PATH)
|
||||
//val itemCSV = CSVFetcher.readCSVasString(ItemCodex.CSV_PATH)
|
||||
//val matCSV = CSVFetcher.readCSVasString(MaterialCodex.CSV_PATH)
|
||||
|
||||
val pathTile = Paths.get("${AppLoader.defaultSaveDir}" +
|
||||
"/$saveDirectoryName/${META_FILENAME_TILE}")
|
||||
val pathItem = Paths.get("${AppLoader.defaultSaveDir}" +
|
||||
"/$saveDirectoryName/${META_FILENAME_ITEM}")
|
||||
val pathMat = Paths.get("${AppLoader.defaultSaveDir}" +
|
||||
"/$saveDirectoryName/${META_FILENAME_MAT}")
|
||||
val tempPathTile = Files.createTempFile(pathTile.toString(), "_temp")
|
||||
val tempPathItem = Files.createTempFile(pathItem.toString(), "_temp")
|
||||
val tempPathMat = Files.createTempFile(pathMat.toString(), "_temp")
|
||||
|
||||
// TODO gzip
|
||||
|
||||
// write CSV to path
|
||||
//Files.write(tempPathTile, tileCSV.toByteArray(Charsets.UTF_8))
|
||||
//Files.write(tempPathItem, itemCSV.toByteArray(Charsets.UTF_8))
|
||||
//Files.write(tempPathMat, matCSV.toByteArray(Charsets.UTF_8))
|
||||
|
||||
// replace savemeta with tempfile
|
||||
try {
|
||||
Files.copy(tempPathTile, pathTile, StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.deleteIfExists(tempPathTile)
|
||||
|
||||
Files.copy(tempPathItem, pathItem, StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.deleteIfExists(tempPathItem)
|
||||
|
||||
Files.copy(tempPathMat, pathMat, StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.deleteIfExists(tempPathMat)
|
||||
|
||||
println("Saved map data '${WriteLayerData.LAYERS_FILENAME}' to $saveDirectoryName.")
|
||||
|
||||
return true
|
||||
}
|
||||
catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
/**
|
||||
* TODO this one does not use TerranVirtualDisk
|
||||
*
|
||||
* Created by minjaesong on 2016-03-18.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
@Deprecated("TEMD is old and removed format; use TEMz which does compression")
|
||||
internal object WriteLayerData {
|
||||
|
||||
init {
|
||||
throw Error("TEMD is old and removed format; use TEMz which does compression")
|
||||
}
|
||||
|
||||
val LAYERS_FILENAME = "worldinfo1"
|
||||
|
||||
/*val MAGIC = "TEMD".toByteArray(charset = Charset.forName("US-ASCII"))
|
||||
|
||||
val BYTE_NULL: Byte = 0
|
||||
|
||||
|
||||
internal operator fun invoke(saveDirectoryName: String): Boolean {
|
||||
val path = "${AppLoader.defaultSaveDir}/$saveDirectoryName/${LAYERS_FILENAME}"
|
||||
val tempPath = "${path}_bak"
|
||||
val map = (Terrarum.ingame!!.world)
|
||||
|
||||
val parentDir = File("${AppLoader.defaultSaveDir}/$saveDirectoryName")
|
||||
if (!parentDir.exists()) {
|
||||
parentDir.mkdir()
|
||||
}
|
||||
else if (!parentDir.isDirectory) {
|
||||
EchoError("Savegame directory is not actually a directory, aborting...")
|
||||
return false
|
||||
}
|
||||
|
||||
val tempFile = File(tempPath)
|
||||
val outFile = File(path)
|
||||
tempFile.createNewFile()
|
||||
|
||||
val outputStream = GZIPOutputStream(FileOutputStream(tempFile))
|
||||
|
||||
|
||||
// write binary
|
||||
outputStream.write(MAGIC)
|
||||
outputStream.write(byteArrayOf(GameWorld.SIZEOF))
|
||||
outputStream.write(byteArrayOf(GameWorld.LAYERS))
|
||||
outputStream.write(byteArrayOf(BYTE_NULL))
|
||||
outputStream.write(byteArrayOf(BYTE_NULL))
|
||||
outputStream.write(map.width.toLittle())
|
||||
outputStream.write(map.height.toLittle())
|
||||
outputStream.write(map.spawnX.toLittle())
|
||||
outputStream.write(map.spawnY.toLittle())
|
||||
// write one row (byteArray) at a time
|
||||
outputStream.write(map.layerTerrain.data)
|
||||
outputStream.write(map.layerWall.data)
|
||||
outputStream.write(map.layerTerrainLowBits.data)
|
||||
outputStream.write(map.layerWallLowBits.data)
|
||||
|
||||
// replace savemeta with tempfile
|
||||
try {
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
outFile.delete()
|
||||
tempFile.copyTo(outFile, overwrite = true)
|
||||
tempFile.delete()
|
||||
println("Saved map data '$LAYERS_FILENAME' to $saveDirectoryName.")
|
||||
|
||||
return true
|
||||
}
|
||||
catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
finally {
|
||||
outputStream.close()
|
||||
}
|
||||
|
||||
return false
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
const val WORLD_WRITER_FORMAT_VERSION = 3
|
||||
@@ -1,208 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.utils.compression.Lzma
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip.FILE_FOOTER
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip.PAYLOAD_FOOTER
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip.PAYLOAD_HEADER
|
||||
import java.io.*
|
||||
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 = WORLD_WRITER_FORMAT_VERSION.toByte()
|
||||
val NUMBER_OF_LAYERS = 3.toByte()
|
||||
val NUMBER_OF_PAYLOADS = 5.toByte()
|
||||
val COMPRESSION_ALGORITHM = 2.toByte()
|
||||
val GENERATOR_VERSION = WORLD_GENERATOR_VERSION.toULittleShort()
|
||||
|
||||
//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 = "${AppLoader.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
|
||||
|
||||
// TODO let's try dump-on-the-disk-then-pack method...
|
||||
|
||||
/*val parentDir = File("${AppLoader.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.toULittle48()) }
|
||||
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); wb(GENERATOR_VERSION)
|
||||
|
||||
// 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)
|
||||
world.layerTerrain.bytesIterator().forEach {
|
||||
val tempByteArray = ByteArray(1)
|
||||
tempByteArray[0] = it
|
||||
val tempByteArrayStream = ByteArrayInputStream(tempByteArray)
|
||||
Lzma.compress(tempByteArrayStream, outputStream)
|
||||
}
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WALL payload
|
||||
wb(PAYLOAD_HEADER); wb("WALL".toByteArray())
|
||||
wi48(world.width * world.height * 3L / 2)
|
||||
world.layerWall.bytesIterator().forEach {
|
||||
val tempByteArray = ByteArray(1)
|
||||
tempByteArray[0] = it
|
||||
val tempByteArrayStream = ByteArrayInputStream(tempByteArray)
|
||||
Lzma.compress(tempByteArrayStream, 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 * 10L)
|
||||
|
||||
|
||||
world.terrainDamages.forEach { t, u ->
|
||||
Lzma.compress(ByteArrayInputStream(t.toULittle48()), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(u.toRawBits().toLittle()), outputStream)
|
||||
}
|
||||
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WdMG payload
|
||||
wb(PAYLOAD_HEADER); wb("WdMG".toByteArray())
|
||||
wi48(world.wallDamages.size * 10L)
|
||||
|
||||
|
||||
world.wallDamages.forEach { t, u ->
|
||||
Lzma.compress(ByteArrayInputStream(t.toULittle48()), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(u.toRawBits().toLittle()), outputStream)
|
||||
}
|
||||
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// FlTP payload
|
||||
wb(PAYLOAD_HEADER); wb("FlTP".toByteArray())
|
||||
wi48(world.fluidTypes.size * 8L)
|
||||
|
||||
|
||||
world.fluidTypes.forEach { t, u ->
|
||||
Lzma.compress(ByteArrayInputStream(t.toULittle48()), outputStream)
|
||||
Lzma.compress(ByteArrayInputStream(u.value.toLittleShort()), outputStream)
|
||||
}
|
||||
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// FlFL payload
|
||||
wb(PAYLOAD_HEADER); wb("FlFL".toByteArray())
|
||||
wi48(world.fluidFills.size * 10L)
|
||||
|
||||
|
||||
world.fluidFills.forEach { t, u ->
|
||||
Lzma.compress(ByteArrayInputStream(t.toULittle48()), 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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import java.util.zip.Deflater
|
||||
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 WriteLayerDataZip {
|
||||
|
||||
// FIXME TERRAIN DAMAGE UNTESTED
|
||||
|
||||
|
||||
// 2400x800 world size, default comp level: about 90 kB
|
||||
// 8192x2048 world size, default comp level: about 670 kB
|
||||
|
||||
// 2400x800 world size, best comp level: about 75 kB
|
||||
// 8192x2048 world size, best comp level: about 555 kB
|
||||
|
||||
val LAYERS_FILENAME = "world"
|
||||
|
||||
val MAGIC = byteArrayOf(0x54, 0x45, 0x4D, 0x7A)
|
||||
val VERSION_NUMBER = WORLD_WRITER_FORMAT_VERSION.toByte()
|
||||
val NUMBER_OF_LAYERS = 3.toByte()
|
||||
val NUMBER_OF_PAYLOADS = 5.toByte()
|
||||
val COMPRESSION_ALGORITHM = 1.toByte()
|
||||
val GENERATOR_VERSION = WORLD_GENERATOR_VERSION.toULittleShort()
|
||||
val PAYLOAD_HEADER = byteArrayOf(0, 0x70, 0x4C, 0x64) // \0pLd
|
||||
val PAYLOAD_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x50, 0x59, 0x4C, 0x64, -1) // EndPYLd\xFF
|
||||
val FILE_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x54, 0x45, 0x4D, -1, -2) // EndTEM with BOM
|
||||
|
||||
//val NULL: Byte = 0
|
||||
|
||||
|
||||
/**
|
||||
* @param world The world to serialise
|
||||
* @param path The directory where the temporary file goes, in relative to the AppLoader.defaultSaveDir. Should NOT start with slashed
|
||||
*
|
||||
* @return File on success; `null` on failure
|
||||
*/
|
||||
internal operator fun invoke(world: GameWorld): ByteArray64? {
|
||||
//val sanitisedPath = path.replace('\\', '/').removePrefix("/").replace("../", "")
|
||||
|
||||
//val path = "${AppLoader.defaultSaveDir}/$sanitisedPath/tmp_$LAYERS_FILENAME${world.worldIndex}"
|
||||
//val path = "${AppLoader.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
|
||||
|
||||
// TODO let's try dump-on-the-disk-then-pack method...
|
||||
|
||||
/*val parentDir = File("${AppLoader.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)
|
||||
val outputStream = ByteArray64GrowableOutputStream()
|
||||
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.toULittle48()) }
|
||||
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); wb(GENERATOR_VERSION)
|
||||
|
||||
// world width, height, and spawn point
|
||||
wi32(world.width); wi32(world.height)
|
||||
wi48(LandUtil.getBlockAddr(world, world.spawnX, world.spawnY))
|
||||
|
||||
// write payloads //
|
||||
|
||||
// TERR payload
|
||||
// PRO Debug tip: every deflated bytes must begin with 0x789C or 0x78DA
|
||||
// Thus, \0pLd + [10] must be either of these.
|
||||
|
||||
// TODO serialised payloads have bit too much zeros, should I be worried?
|
||||
|
||||
val savePayloads = world.javaClass.superclass.declaredFields//.filter { it.isAnnotationPresent(TEMzPayload::class.java) }
|
||||
printdbg(this, "")
|
||||
savePayloads.forEach { printdbg(this, "${it.name}: ${it.type} @${it.declaredAnnotations.size}") }
|
||||
|
||||
wb(PAYLOAD_HEADER); wb("TERR".toByteArray())
|
||||
wi48(world.width * world.height * 3L / 2)
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
world.layerTerrain.bytesIterator().forEach { deflater.write(it.toInt()) }
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WALL payload
|
||||
wb(PAYLOAD_HEADER); wb("WALL".toByteArray())
|
||||
wi48(world.width * world.height * 3L / 2)
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
world.layerWall.bytesIterator().forEach { deflater.write(it.toInt()) }
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WIRE payload
|
||||
/*wb(PAYLOAD_HEADER); wb("WIRE".toByteArray())
|
||||
wi48(world.width * world.height.toLong())
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
deflater.write(world.wireArray)
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)*/
|
||||
|
||||
// TdMG payload
|
||||
wb(PAYLOAD_HEADER); wb("TdMG".toByteArray())
|
||||
wi48(world.terrainDamages.size * 10L)
|
||||
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
|
||||
world.terrainDamages.forEach { t, u ->
|
||||
deflater.write(t.toULittle48())
|
||||
deflater.write(u.toRawBits().toLittle())
|
||||
}
|
||||
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// WdMG payload
|
||||
wb(PAYLOAD_HEADER); wb("WdMG".toByteArray())
|
||||
wi48(world.wallDamages.size * 10L)
|
||||
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
|
||||
world.wallDamages.forEach { t, u ->
|
||||
deflater.write(t.toULittle48())
|
||||
deflater.write(u.toRawBits().toLittle())
|
||||
}
|
||||
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// FlTP payload
|
||||
wb(PAYLOAD_HEADER); wb("FlTP".toByteArray())
|
||||
wi48(world.fluidTypes.size * 8L)
|
||||
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
|
||||
world.fluidTypes.forEach { t, u ->
|
||||
deflater.write(t.toULittle48())
|
||||
deflater.write(u.value.toLittleShort())
|
||||
}
|
||||
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
// FlFL payload
|
||||
wb(PAYLOAD_HEADER); wb("FlFL".toByteArray())
|
||||
wi48(world.fluidFills.size * 10L)
|
||||
|
||||
deflater = DeflaterOutputStream(outputStream, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
|
||||
world.fluidFills.forEach { t, u ->
|
||||
deflater.write(t.toULittle48())
|
||||
deflater.write(u.toRawBits().toLittle())
|
||||
}
|
||||
|
||||
deflater.flush(); deflater.finish()
|
||||
wb(PAYLOAD_FOOTER)
|
||||
|
||||
|
||||
// write footer
|
||||
wb(FILE_FOOTER)
|
||||
|
||||
|
||||
//////////////////
|
||||
// END OF WRITE //
|
||||
//////////////////
|
||||
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
return outputStream.toByteArray64()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,70 +1,73 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen
|
||||
import java.nio.charset.Charset
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonWriter
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.blockproperties.WireCodex
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||
import net.torvald.terrarum.itemproperties.MaterialCodex
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumIngame
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
|
||||
import net.torvald.terrarum.weather.WeatherMixer
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-15.
|
||||
*/
|
||||
// internal for everything: prevent malicious module from messing up the savedata
|
||||
internal object WriteMeta {
|
||||
open class WriteMeta(val ingame: TerrarumIngame) {
|
||||
|
||||
val META_FILENAME = "worldinfo0"
|
||||
open fun invoke(): String {
|
||||
val world = ingame.world
|
||||
|
||||
val props = hashMapOf<String, Any>(
|
||||
"genver" to 4,
|
||||
"savename" to world.worldName,
|
||||
"terrseed" to world.generatorSeed,
|
||||
"randseed0" to RoguelikeRandomiser.RNG.state0,
|
||||
"randseed1" to RoguelikeRandomiser.RNG.state1,
|
||||
"weatseed0" to WeatherMixer.RNG.state0,
|
||||
"weatseed1" to WeatherMixer.RNG.state1,
|
||||
"playerid" to ingame.theRealGamer.referenceID,
|
||||
"creation_t" to world.creationTime,
|
||||
"lastplay_t" to world.lastPlayTime,
|
||||
"playtime_t" to world.totalPlayTime,
|
||||
|
||||
val MAGIC = "TESV".toByteArray(charset = Charset.forName("US-ASCII"))
|
||||
// CSVs
|
||||
"blocks" to StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("blocks/blocks.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
it.toString()
|
||||
},
|
||||
|
||||
"items" to StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("items/itemid.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
it.toString()
|
||||
},
|
||||
|
||||
val BYTE_NULL: Byte = 0
|
||||
"wires" to StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("wires/wires.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
it.toString()
|
||||
},
|
||||
|
||||
val terraseed: Long = Worldgen.params.seed
|
||||
// TODO fluids
|
||||
"materials" to StringBuilder().let {
|
||||
ModMgr.getFilesFromEveryMod("materials/materials.csv").forEach { (modname, file) ->
|
||||
it.append("\n\n## module: $modname ##\n\n")
|
||||
it.append(file.readText())
|
||||
}
|
||||
it.toString()
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Write save meta to specified directory. Returns false if something went wrong.
|
||||
* @param saveDirectoryName
|
||||
* @param savegameName -- Nullable. If the value is not specified, saveDirectoryName will be used instead.
|
||||
*/
|
||||
internal fun write(saveDirectoryName: String, savegameName: String?): Boolean {
|
||||
/*val hashArray: ArrayList<ByteArray> = ArrayList()
|
||||
val savenameAsByteArray: ByteArray =
|
||||
(savegameName ?: saveDirectoryName).toByteArray(Charsets.UTF_8)
|
||||
|
||||
// define Strings to be hashed
|
||||
val props = arrayOf(
|
||||
TilePropCSV()
|
||||
//, (item, mat, ...)
|
||||
"loadorder" to ModMgr.loadOrder,
|
||||
"worlds" to ingame.gameworldIndices
|
||||
)
|
||||
|
||||
// get and store hash from the list
|
||||
props.map { hashArray.add(DigestUtils.sha256(it)) }
|
||||
|
||||
// open file and delete it
|
||||
val metaPath = Paths.get("$AppLoader.defaultSaveDir" +
|
||||
"/$saveDirectoryName/$META_FILENAME")
|
||||
val metaTempPath = Files.createTempFile(metaPath.toString(), "_temp")
|
||||
|
||||
// TODO gzip
|
||||
|
||||
// write bytes in tempfile
|
||||
Files.write(metaTempPath, MAGIC)
|
||||
Files.write(metaTempPath, savenameAsByteArray)
|
||||
Files.write(metaTempPath, byteArrayOf(BYTE_NULL))
|
||||
Files.write(metaTempPath, toByteArray(terraseed))
|
||||
Files.write(metaTempPath, toByteArray(rogueseed))
|
||||
for (hash in hashArray)
|
||||
Files.write(metaTempPath, hash)
|
||||
|
||||
// replace savemeta with tempfile
|
||||
try {
|
||||
Files.copy(metaTempPath, metaPath, StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.deleteIfExists(metaTempPath)
|
||||
println("Saved metadata to $saveDirectoryName.")
|
||||
|
||||
return true
|
||||
}
|
||||
catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}*/
|
||||
return false
|
||||
|
||||
return Json(JsonWriter.OutputType.json).toJson(props)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.GL30
|
||||
import com.badlogic.gdx.graphics.OrthographicCamera
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import com.badlogic.gdx.graphics.glutils.FrameBuffer
|
||||
import com.badlogic.gdx.utils.ScreenUtils
|
||||
import net.torvald.gdx.graphics.PixmapIO2
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||
import net.torvald.terrarum.weather.WeatherMixer
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
|
||||
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 org.apache.commons.codec.digest.DigestUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.Deflater
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
|
||||
object WriteWorldInfo {
|
||||
|
||||
val META_MAGIC = "TESV".toByteArray(Charsets.UTF_8)
|
||||
val NULL = 0.toByte()
|
||||
|
||||
val VERSION = 1
|
||||
val HASHED_FILES_COUNT = 3
|
||||
|
||||
/**
|
||||
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
|
||||
*
|
||||
* @return List of ByteArray64, worldinfo0..worldinfo3; `null` on failure
|
||||
*/
|
||||
internal operator fun invoke(ingame: IngameInstance): List<ByteArray64>? {
|
||||
|
||||
//val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
|
||||
|
||||
val infileList = arrayOf(
|
||||
ModMgr.getGdxFilesFromEveryMod("blocks/blocks.csv"),
|
||||
ModMgr.getGdxFilesFromEveryMod("items/items.csv"),
|
||||
ModMgr.getGdxFilesFromEveryMod("materials/materials.csv")
|
||||
)
|
||||
|
||||
val outFiles = ArrayList<ByteArray64>() // for worldinfo1-3 only
|
||||
val worldInfoHash = ArrayList<ByteArray>() // hash of worldinfo1-3
|
||||
// try to write worldinfo1-3
|
||||
|
||||
for (filenum in 1..HASHED_FILES_COUNT) {
|
||||
val outputStream = ByteArray64GrowableOutputStream()
|
||||
val infile = infileList[filenum - 1]
|
||||
|
||||
infile.forEach {
|
||||
outputStream.write("## from file: ${it.second.nameWithoutExtension()} ##############################\n".toByteArray())
|
||||
val readBytes = it.second.readBytes()
|
||||
outputStream.write(readBytes)
|
||||
outputStream.write("\n".toByteArray())
|
||||
}
|
||||
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
|
||||
outFiles.add(outputStream.toByteArray64())
|
||||
|
||||
|
||||
worldInfoHash.add(DigestUtils.sha256(ByteArray64InputStream(outputStream.toByteArray64())))
|
||||
}
|
||||
|
||||
|
||||
// compose save meta (actual writing part)
|
||||
val metaOut = ByteArray64GrowableOutputStream()
|
||||
|
||||
|
||||
metaOut.write(META_MAGIC)
|
||||
metaOut.write(VERSION)
|
||||
metaOut.write(HASHED_FILES_COUNT)
|
||||
|
||||
// world name
|
||||
val world = ingame.world
|
||||
val worldNameBytes = world.worldName.toByteArray(Charsets.UTF_8)
|
||||
//metaOut.write(worldNameBytes)
|
||||
worldNameBytes.forEach {
|
||||
if (it != 0.toByte()) metaOut.write(it.toInt())
|
||||
}
|
||||
metaOut.write(NULL.toInt())
|
||||
|
||||
// terrain seed
|
||||
metaOut.write(world.generatorSeed.toLittle())
|
||||
|
||||
// randomiser seed
|
||||
metaOut.write(RoguelikeRandomiser.RNG.state0.toLittle())
|
||||
metaOut.write(RoguelikeRandomiser.RNG.state1.toLittle())
|
||||
|
||||
// weather seed
|
||||
metaOut.write(WeatherMixer.RNG.state0.toLittle())
|
||||
metaOut.write(WeatherMixer.RNG.state1.toLittle())
|
||||
|
||||
// reference ID of the player
|
||||
metaOut.write(Terrarum.PLAYER_REF_ID.toLittle())
|
||||
|
||||
// ingame time_t
|
||||
metaOut.write((world as GameWorldExtension).worldTime.TIME_T.toLittle())
|
||||
|
||||
// creation time (real world time)
|
||||
metaOut.write(world.creationTime.toULittle48())
|
||||
|
||||
// time at save (real world time)
|
||||
val timeNow = System.currentTimeMillis() / 1000L
|
||||
metaOut.write(timeNow.toULittle48())
|
||||
|
||||
// get playtime and save it
|
||||
val timeToAdd = (timeNow - world.loadTime).toInt()
|
||||
metaOut.write((world.totalPlayTime + timeToAdd).toLittle())
|
||||
world.lastPlayTime = timeNow
|
||||
world.totalPlayTime += timeToAdd
|
||||
|
||||
// SHA256SUM of worldinfo1-3
|
||||
worldInfoHash.forEach {
|
||||
metaOut.write(it)
|
||||
}
|
||||
|
||||
// thumbnail
|
||||
val texreg = ingame.actorGamer.sprite?.textureRegion
|
||||
if (texreg != null) {
|
||||
val batch = SpriteBatch()
|
||||
val camera = OrthographicCamera(texreg.tileW.toFloat(), texreg.tileH.toFloat())
|
||||
val fbo = FrameBuffer(Pixmap.Format.RGBA8888, texreg.tileW, texreg.tileH, false)
|
||||
|
||||
fbo.inAction(camera, batch) {
|
||||
batch.inUse {
|
||||
batch.draw(texreg.get(0, 0), 0f, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
// bind and unbind the fbo so that I can get the damned Pixmap using ScreenUtils
|
||||
// NullPointerException if not appconfig.useGL30
|
||||
Gdx.gl30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, fbo.framebufferHandle)
|
||||
Gdx.gl30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0)
|
||||
|
||||
val outpixmap = ScreenUtils.getFrameBufferPixmap(0, 0, fbo.width, fbo.height)
|
||||
|
||||
Gdx.gl30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0)
|
||||
Gdx.gl30.glReadBuffer(GL30.GL_BACK)
|
||||
|
||||
|
||||
val tgaSize = PixmapIO2.HEADER_FOOTER_SIZE + outpixmap.width * outpixmap.height * 4
|
||||
val byteArrayOS = ByteArrayOutputStream(tgaSize)
|
||||
PixmapIO2._writeTGA(byteArrayOS, outpixmap, true, true)
|
||||
byteArrayOS.flush()
|
||||
byteArrayOS.close()
|
||||
|
||||
|
||||
//PixmapIO2.writeTGA(Gdx.files.absolute(AppLoader.defaultDir+"/tmp_writeworldinfo+outpixmap.tga"), outpixmap, true)
|
||||
|
||||
|
||||
outpixmap.dispose()
|
||||
batch.dispose()
|
||||
fbo.dispose()
|
||||
|
||||
|
||||
|
||||
// write uncompressed size
|
||||
metaOut.write(tgaSize.toULittleShort())
|
||||
// write compressed tga
|
||||
val deflater = DeflaterOutputStream(metaOut, Deflater(Deflater.BEST_COMPRESSION, true), false)
|
||||
deflater.write(byteArrayOS.toByteArray())
|
||||
deflater.flush(); deflater.finish()
|
||||
// write footer
|
||||
metaOut.write(-1); metaOut.write(-2)
|
||||
}
|
||||
|
||||
// more data goes here //
|
||||
|
||||
|
||||
metaOut.flush()
|
||||
metaOut.close()
|
||||
|
||||
|
||||
|
||||
return listOf(metaOut.toByteArray64()) + outFiles.toList()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user