gson test in progress

This commit is contained in:
minjaesong
2019-02-22 04:26:19 +09:00
parent f24ddb5c82
commit 53737bd746
46 changed files with 379 additions and 3061 deletions

BIN
lib/TerranVirtualDisk.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/gson-2.8.5.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -11,7 +11,10 @@ import net.torvald.terrarum.Second
import net.torvald.terrarum.gameactors.ActorWBMovable
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
class SpriteAnimation(val parentActor: ActorWBMovable) {
/**
* This class should not be serialised; save its Animation Description Language instead.
*/
class SpriteAnimation(@Transient val parentActor: ActorWBMovable) {
lateinit var textureRegion: TextureRegionPack; private set

View File

@@ -31,11 +31,15 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
*
* Most of the time it'd be the "player", but think about the case where you have possessed
* some random actor of the game. Now that actor is now actorNowPlaying, the actual gamer's avatar
* (reference ID of 0x91A7E2) (must) stay in the actorContainer, but it's not a actorNowPlaying.
* (reference ID of 0x91A7E2) (must) stay in the actorContainerActive, but it's not a actorNowPlaying.
*
* Nullability of this property is believed to be unavoidable (trust me!). I'm sorry for the inconvenience.
*/
open var actorNowPlaying: ActorHumanoid? = null
/**
* The actual gamer
*/
open var actorGamer: ActorHumanoid? = null
open var gameInitialised = false
internal set
@@ -43,7 +47,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
internal set
val ACTORCONTAINER_INITIAL_SIZE = 64
val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val actorContainerActive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
protected val terrainChangeQueue = Queue<BlockChangeQueueItem>()
@@ -135,10 +139,10 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
///////////////////////
fun getActorByID(ID: Int): Actor {
if (actorContainer.size == 0 && actorContainerInactive.size == 0)
if (actorContainerActive.size == 0 && actorContainerInactive.size == 0)
throw IllegalArgumentException("Actor with ID $ID does not exist.")
var index = actorContainer.binarySearch(ID)
var index = actorContainerActive.binarySearch(ID)
if (index < 0) {
index = actorContainerInactive.binarySearch(ID)
@@ -154,7 +158,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
return actorContainerInactive[index]
}
else
return actorContainer[index]
return actorContainerActive[index]
}
fun ArrayList<*>.binarySearch(actor: Actor) = this.binarySearch(actor.referenceID!!)
@@ -191,9 +195,9 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
open fun removeActor(actor: Actor?) {
if (actor == null) return
val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
val indexToDelete = actorContainerActive.binarySearch(actor.referenceID!!)
if (indexToDelete >= 0) {
actorContainer.removeAt(indexToDelete)
actorContainerActive.removeAt(indexToDelete)
}
}
@@ -207,16 +211,16 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
throw Error("The actor $actor already exists in the game")
}
else {
actorContainer.add(actor)
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
actorContainerActive.add(actor)
insertionSortLastElem(actorContainerActive) // we can do this as we are only adding single actor
}
}
fun isActive(ID: Int): Boolean =
if (actorContainer.size == 0)
if (actorContainerActive.size == 0)
false
else
actorContainer.binarySearch(ID) >= 0
actorContainerActive.binarySearch(ID) >= 0
fun isInactive(ID: Int): Boolean =
if (actorContainerInactive.size == 0)
@@ -225,7 +229,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
actorContainerInactive.binarySearch(ID) >= 0
/**
* actorContainer extensions
* actorContainerActive extensions
*/
fun theGameHasActor(actor: Actor?) = if (actor == null) false else theGameHasActor(actor.referenceID!!)

View File

@@ -1,12 +1,8 @@
package net.torvald.terrarum
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import kotlin.collections.HashMap
typealias ItemValue = KVHashMap
@@ -116,6 +112,7 @@ open class KVHashMap : GsonSerialisable {
}
override fun read(gson: JsonObject) {
TODO()
}
override fun write(targetGson: JsonObject) {

View File

@@ -35,7 +35,7 @@ abstract class Actor(val renderOrder: RenderOrder) : Comparable<Actor>, Runnable
* @return Reference ID. (16777216-0x7FFF_FFFF)
*/
open var referenceID: ActorID? = null
var actorValue = ActorValue(this)
var actorValue = ActorValue(this) // FIXME cyclic reference on GSON
@Volatile var flagDespawn = false
override fun equals(other: Any?): Boolean {

View File

@@ -5,7 +5,7 @@ import net.torvald.terrarum.KVHashMap
/**
* Created by minjaesong on 2017-04-28.
*/
class ActorValue(val actor: Actor) : KVHashMap() {
class ActorValue(@Transient val actor: Actor) : KVHashMap() {
private constructor(actor: Actor, newMap: HashMap<String, Any>): this(actor) {
hashMap = newMap

View File

@@ -32,7 +32,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
ActorWithBody(renderOrder) {
val COLLISION_TEST_MODE = false
@Transient val COLLISION_TEST_MODE = false
/* !! ActorValue macros are on the very bottom of the source !! */
@@ -1155,15 +1155,15 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
}
}*/
private inline val submergedRatio: Double
private val submergedRatio: Double
get() {
if (hitbox.height == 0.0) throw RuntimeException("Hitbox.height is zero")
return submergedHeight / hitbox.height
}
private inline val submergedVolume: Double
private val submergedVolume: Double
get() = submergedHeight * hitbox.width * hitbox.width
private inline val submergedHeight: Double
private val submergedHeight: Double
get() = Math.max(
getContactingAreaFluid(COLLIDING_LEFT),
getContactingAreaFluid(COLLIDING_RIGHT)

View File

@@ -2,6 +2,7 @@
package net.torvald.terrarum.gameworld
import com.badlogic.gdx.graphics.Color
import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.blockproperties.BlockCodex
@@ -17,7 +18,19 @@ typealias BlockAddress = Long
open class GameWorld {
var worldName: String = "New World"
/** Index start at 1 */
var worldIndex: Int
set(value) {
if (value <= 0)
throw Error("World index start at 1; you entered $value")
printdbg(this, "Creation of new world with index $value, called by:")
Thread.currentThread().stackTrace.forEach {
printdbg(this, "--> $it")
}
field = value
}
val width: Int
val height: Int
@@ -451,7 +464,7 @@ open class GameWorld {
@Transient val SIZEOF: Byte = MapLayer.SIZEOF
@Transient val LAYERS: Byte = 4 // terrain, wall (layerTerrainLowBits + layerWallLowBits), wire
fun makeNullWorld() = GameWorld(-1, 1, 1, 0, 0, 0)
fun makeNullWorld() = GameWorld(1, 1, 1, 0, 0, 0)
}
}

View File

@@ -74,9 +74,11 @@ abstract class GameItem : Comparable<GameItem>, Cloneable {
abstract val isDynamic: Boolean
/**
* Where to equip the item
* Where to equip the item.
*
* Can't use 'open val' as GSON don't like that
*/
open val equipPosition: Int = EquipPosition.NULL
var equipPosition: Int = EquipPosition.NULL
abstract val material: Material
@@ -273,6 +275,7 @@ abstract class GameItem : Comparable<GameItem>, Cloneable {
fun generateUniqueDynamicID(inventory: ActorInventory): GameItem {
dynamicID = GameItem.generateUniqueDynamicID(inventory)
ItemCodex.registerNewDynamicItem(dynamicID, this)
return this
}
@@ -285,6 +288,7 @@ abstract class GameItem : Comparable<GameItem>, Cloneable {
do {
ret = ITEM_DYNAMIC.pickRandom()
} while (inventory.contains(ret))
return ret
}
}

View File

@@ -2,7 +2,8 @@ package net.torvald.terrarum.itemproperties
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.KVHashMap
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameworld.GameWorld
@@ -21,7 +22,7 @@ object ItemCodex {
* Will return corresponding Actor if ID >= ACTORID_MIN
*/
val itemCodex = HashMap<ItemID, GameItem>()
private val dynamicItemDescription = HashMap<ItemID, KVHashMap>()
val dynamicItemDescription = HashMap<ItemID, GameItem>()
val ITEM_TILES = 0..GameWorld.TILES_SUPPORTED - 1
val ITEM_WALLS = GameWorld.TILES_SUPPORTED..GameWorld.TILES_SUPPORTED * 2 - 1
@@ -73,7 +74,7 @@ object ItemCodex {
// check for collision with actors (BLOCK only)
if (this.inventoryCategory == Category.BLOCK) {
ingame.actorContainer.forEach {
ingame.actorContainerActive.forEach {
if (it is ActorWBMovable && it.hIntTilewiseHitbox.intersects(mousePoint))
return false
}
@@ -141,7 +142,7 @@ object ItemCodex {
// linear search filter (check for intersection with tilewise mouse point and tilewise hitbox)
// return false if hitting actors
ingame.actorContainer.forEach {
ingame.actorContainerActive.forEach {
if (it is ActorWBMovable && it.hIntTilewiseHitbox.intersects(mousePoint))
return false
}
@@ -188,7 +189,9 @@ object ItemCodex {
override val isDynamic: Boolean = false
override val material: Material = Material(1,1,1,1,1,1,1,1,1,1.0)
override val equipPosition: Int = EquipPosition.HAND_GRIP
init {
equipPosition = EquipPosition.HAND_GRIP
}
override fun startPrimaryUse(delta: Float): Boolean {
val ingame = Terrarum.ingame!! as Ingame // must be in here
@@ -215,7 +218,9 @@ object ItemCodex {
override val isDynamic: Boolean = false
override val material: Material = Material(1,1,1,1,1,1,1,1,1,1.0)
override val equipPosition: Int = EquipPosition.HAND_GRIP
init {
equipPosition = EquipPosition.HAND_GRIP
}
override fun startPrimaryUse(delta: Float): Boolean {
val ingame = Terrarum.ingame!! as Ingame // must be in here
@@ -232,14 +237,22 @@ object ItemCodex {
println()
}
fun registerNewDynamicItem(dynamicID: Int, item: GameItem) {
if (AppLoader.IS_DEVELOPMENT_BUILD) {
printdbg(this, "Registering new dynamic item $dynamicID (from ${item.originalID})")
}
dynamicItemDescription[dynamicID] = item
}
/**
* Returns clone of the item in the Codex
* Returns the item in the Codex. If the item is static, its clone will be returned (you are free to modify the returned item).
* However, if the item is dynamic, the item itself will be returned. Modifying the item will affect the game.
*/
operator fun get(code: ItemID): GameItem {
if (code <= ITEM_STATIC.endInclusive) // generic item
return itemCodex[code]!!.clone() // from CSV
else if (code <= ITEM_DYNAMIC.endInclusive) {
TODO("read from dynamicitem description (JSON)")
return itemCodex[code]!!
}
else {
val a = (Terrarum.ingame!! as Ingame).getActorByID(code) // actor item

View File

@@ -42,7 +42,6 @@ class EntryPoint : ModuleEntryPoint() {
override val isUnique: Boolean = false
override var baseMass: Double = BlockCodex[i].density / 1000.0
override var baseToolSize: Double? = null
override var equipPosition = EquipPosition.HAND_GRIP
override val originalName = BlockCodex[i % ItemCodex.ITEM_WALLS.first].nameKey
override var stackable = true
override var inventoryCategory = if (i in ItemCodex.ITEM_TILES) Category.BLOCK else Category.WALL
@@ -50,6 +49,8 @@ class EntryPoint : ModuleEntryPoint() {
override val material = Material(0,0,0,0,0,0,0,0,0,0.0)
init {
equipPosition = EquipPosition.HAND_GRIP
if (IS_DEVELOPMENT_BUILD)
print("$originalID ")
}
@@ -61,7 +62,7 @@ class EntryPoint : ModuleEntryPoint() {
// check for collision with actors (BLOCK only)
if (this.inventoryCategory == Category.BLOCK) {
ingame.actorContainer.forEach {
ingame.actorContainerActive.forEach {
if (it is ActorWBMovable && it.hIntTilewiseHitbox.intersects(mousePoint))
return false
}

View File

@@ -52,7 +52,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
*/
//val ACTORCONTAINER_INITIAL_SIZE = 64
val PARTICLES_MAX = AppLoader.getConfigInt("maxparticles")
//val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
//val actorContainerActive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
//val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val particlesContainer = CircularArray<ParticleBase>(PARTICLES_MAX)
val uiContainer = ArrayList<UICanvas>()
@@ -156,6 +156,12 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
lateinit var gameworld: GameWorldExtension
lateinit var theRealGamer: IngamePlayer
override var actorGamer: ActorHumanoid?
get() = theRealGamer
set(value) {
throw UnsupportedOperationException()
}
enum class GameLoadMode {
CREATE_NEW, LOAD_FROM
}
@@ -611,10 +617,10 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
* if the actor is not to be dormant, it will be just ignored.
*/
fun KillOrKnockdownActors() {
var actorContainerSize = actorContainer.size
var actorContainerSize = actorContainerActive.size
var i = 0
while (i < actorContainerSize) { // loop through actorContainer
val actor = actorContainer[i]
while (i < actorContainerSize) { // loop through actorContainerActive
val actor = actorContainerActive[i]
val actorIndex = i
// kill actors flagged to despawn
if (actor.flagDespawn) {
@@ -627,7 +633,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
if (actor !is Projectile) { // if it's a projectile, don't inactivate it; just kill it.
actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated
}
actorContainer.removeAt(actorIndex)
actorContainerActive.removeAt(actorIndex)
actorContainerSize -= 1
i-- // array removed 1 elem, so we also decrement counter by 1
}
@@ -641,8 +647,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
* NOTE: concurrency for actor updating is currently disabled because of it's poor performance
*/
fun updateActors(delta: Float) {
if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD && actorContainer.size > Terrarum.THREADS) {
val actors = actorContainer.size.toFloat()
if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD && actorContainerActive.size > Terrarum.THREADS) {
val actors = actorContainerActive.size.toFloat()
// set up indices
for (i in 0..Terrarum.THREADS - 1) {
ThreadParallel.map(
@@ -659,7 +665,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
actorNowPlaying?.update(delta)
}
else {
actorContainer.forEach {
actorContainerActive.forEach {
if (it != actorNowPlaying) {
it.update(delta)
@@ -736,12 +742,12 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
if (actor.referenceID == theRealGamer.referenceID || actor.referenceID == 0x51621D) // do not delete this magic
throw RuntimeException("Attempted to remove player.")
val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
val indexToDelete = actorContainerActive.binarySearch(actor.referenceID!!)
if (indexToDelete >= 0) {
printdbg(this, "Removing actor $actor")
printStackTrace()
actorContainer.removeAt(indexToDelete)
actorContainerActive.removeAt(indexToDelete)
// indexToDelete >= 0 means that the actor certainly exists in the game
// which means we don't need to check if i >= 0 again
@@ -785,8 +791,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
printdbg(this, "Adding actor $actor")
printStackTrace()
actorContainer.add(actor)
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
actorContainerActive.add(actor)
insertionSortLastElem(actorContainerActive) // we can do this as we are only adding single actor
if (actor is ActorWithBody) {
when (actor.renderOrder) {
@@ -819,8 +825,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
}
else {
actorContainerInactive.remove(actor)
actorContainer.add(actor)
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
actorContainerActive.add(actor)
insertionSortLastElem(actorContainerActive) // we can do this as we are only adding single actor
if (actor is ActorWithBody) {
when (actor.renderOrder) {
@@ -959,7 +965,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
private fun printStackTrace() {
Thread.currentThread().getStackTrace().forEach {
printdbg(this, "-> $it")
printdbg(this, "--> $it")
}
}

View File

@@ -15,7 +15,7 @@ internal object ActorsList : ConsoleCommand {
override fun execute(args: Array<String>) {
jPanelInstances.add(ActorsLister(
(Terrarum.ingame!! as Ingame).actorContainer,
(Terrarum.ingame!! as Ingame).actorContainerActive,
(Terrarum.ingame!! as Ingame).actorContainerInactive)
)
}

View File

@@ -2,17 +2,13 @@ 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
import net.torvald.terrarum.serialise.WriteWorldInfo
/**
* Created by minjaesong on 2017-07-18.
*/
object ExportLayerData : ConsoleCommand {
override fun execute(args: Array<String>) {
try {
/*try {
val outfile = WriteLayerDataZip()
WriteWorldInfo()
Echo("Layer data exported to ${outfile!!.canonicalPath}")
@@ -20,7 +16,7 @@ object ExportLayerData : ConsoleCommand {
catch (e: Exception) {
e.printStackTrace()
EchoError("Layer data export failed; see console for error traces.")
}
}*/
}
override fun printUsage() {

View File

@@ -1,12 +1,11 @@
package net.torvald.terrarum.modulebasegame.console
import net.torvald.terrarum.Terrarum
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.console.ConsoleCommand
import net.torvald.terrarum.console.Echo
import net.torvald.terrarum.modulebasegame.Ingame
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.IOException
@@ -17,9 +16,25 @@ import java.io.IOException
internal object GsonTest : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
val avelem = Gson().toJsonTree((Terrarum.ingame!! as Ingame).actorNowPlaying)
val jsonString = avelem.toString()
val jsonBuilder = if (AppLoader.IS_DEVELOPMENT_BUILD) {
GsonBuilder()
.setPrettyPrinting()
.serializeNulls()
.create()
}
else {
GsonBuilder()
.serializeNulls()
.create()
}
val jsonString = jsonBuilder.toJson((Terrarum.ingame!! as Ingame).actorNowPlaying)
//val avelem = Gson().toJson((Terrarum.ingame!! as Ingame).actorNowPlaying)
//val jsonString = avelem.toString()
val bufferedWriter: BufferedWriter
val writer: FileWriter

View File

@@ -153,7 +153,7 @@ open class ActorHumanoid(
get() = if (Terrarum.ingame == null) false else this == Terrarum.ingame!!.actorNowPlaying
private val nullItem = object : GameItem() {
@Transient private val nullItem = object : GameItem() {
override var dynamicID: Int = 0
override val originalID = dynamicID
override val isUnique: Boolean = false

View File

@@ -20,14 +20,16 @@ import java.util.concurrent.locks.ReentrantLock
* Created by minjaesong on 2016-03-15.
*/
class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode: Int) {
class ActorInventory(@Transient val actor: Pocketed, var maxCapacity: Int, var capacityMode: Int) {
companion object {
@Transient val CAPACITY_MODE_NO_ENCUMBER = 0
@Transient val CAPACITY_MODE_COUNT = 1
@Transient val CAPACITY_MODE_WEIGHT = 2
val CAPACITY_MODE_NO_ENCUMBER = 0
val CAPACITY_MODE_COUNT = 1
val CAPACITY_MODE_WEIGHT = 2
}
// FIXME unless absolutely necessary, don't store full item object; only store its dynamicID
/**
* List of all equipped items (tools, armours, rings, necklaces, etc.)
*/
@@ -265,8 +267,8 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
arr[j + 1] = x
}
}
private val STATIC_ID = 41324534
private val DYNAMIC_ID = 181643953
@Transient private val STATIC_ID = 41324534
@Transient private val DYNAMIC_ID = 181643953
private fun ArrayList<InventoryPair>.binarySearch(ID: ItemID, searchBy: Int): Int {
// code from collections/Collections.kt
var low = 0

View File

@@ -31,8 +31,8 @@ open class ParticleBase(renderOrder: Actor.RenderOrder, val despawnUponCollision
private val lifetimeMax = maxLifeTime ?: 5f
private var lifetimeCounter = 0f
open val velocity = Vector2(0.0, 0.0)
open val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0)
val velocity = Vector2(0.0, 0.0)
val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0)
open lateinit var body: TextureRegion // you might want to use SpriteAnimation
open var glow: TextureRegion? = null
@@ -61,7 +61,7 @@ open class ParticleBase(renderOrder: Actor.RenderOrder, val despawnUponCollision
// gravity, winds, etc. (external forces)
if (!isNoSubjectToGrav) {
velocity += (Terrarum.ingame!!.world).gravitation / dragCoefficient
velocity.plusAssign((Terrarum.ingame!!.world).gravitation / dragCoefficient)
}

View File

@@ -1,7 +1,5 @@
package net.torvald.terrarum.modulebasegame.gameactors
import net.torvald.terrarum.gameactors.Controllable
/**
* A wrapper to support instant player changing (or possessing other NPCs maybe)
*
@@ -9,7 +7,7 @@ import net.torvald.terrarum.gameactors.Controllable
* Created by minjaesong on 2016-10-23.
*/
@Deprecated("The ingame should discriminate 'theRealGamer' and 'actorNowPlaying'")
class PlayableActorDelegate(val actor: ActorHumanoid) {
class PlayableActorDelegate(@Transient val actor: ActorHumanoid) {
/*init {
if (actor !is Controllable)

View File

@@ -9,7 +9,7 @@ import net.torvald.terrarum.Terrarum
class ThreadActorUpdate(val startIndex: Int, val endIndex: Int) : Runnable {
override fun run() {
for (i in startIndex..endIndex) {
val it = Terrarum.ingame!!.actorContainer[i]
val it = Terrarum.ingame!!.actorContainerActive[i]
it.update(AppLoader.UPDATE_RATE.toFloat())
if (it is Pocketed) {

View File

@@ -39,7 +39,7 @@ object CollisionSolver {
collCandidateY.clear()
// mark list x
(Terrarum.ingame!! as Ingame).actorContainer.forEach { it ->
(Terrarum.ingame!! as Ingame).actorContainerActive.forEach { it ->
if (it is ActorWBMovable) {
collListX.add(CollisionMarkings(it.hitbox.hitboxStart.x, STARTPOINT, it))
collListX.add(CollisionMarkings(it.hitbox.hitboxEnd.x, ENDPOINT, it))
@@ -72,7 +72,7 @@ object CollisionSolver {
collCandidateStack.clear()
// mark list y
(Terrarum.ingame!! as Ingame).actorContainer.forEach { it ->
(Terrarum.ingame!! as Ingame).actorContainerActive.forEach { it ->
if (it is ActorWBMovable) {
collListY.add(CollisionMarkings(it.hitbox.hitboxStart.y, STARTPOINT, it))
collListY.add(CollisionMarkings(it.hitbox.hitboxEnd.y, ENDPOINT, it))

View File

@@ -21,15 +21,15 @@ class PickaxeGeneric(override val originalID: ItemID) : GameItem() {
override var baseMass = 10.0
override var baseToolSize: Double? = 10.0
override var stackable = true
override var maxDurability = 147
override var durability = maxDurability.toFloat()
override val equipPosition = GameItem.EquipPosition.HAND_GRIP
override var inventoryCategory = Category.TOOL
override val isUnique = false
override val isDynamic = true
override val material = Material(0,0,0,0,0,0,0,0,1,0.0)
override var isUnique = false
override var isDynamic = true
override var material = Material(0,0,0,0,0,0,0,0,1,0.0)
init {
super.equipPosition = GameItem.EquipPosition.HAND_GRIP
super.maxDurability = 147
super.durability = maxDurability.toFloat()
super.name = "Builtin Pickaxe"
}
@@ -47,7 +47,7 @@ class PickaxeGeneric(override val originalID: ItemID) : GameItem() {
// linear search filter (check for intersection with tilewise mouse point and tilewise hitbox)
// return false if hitting actors
Terrarum.ingame!!.actorContainer.forEach {
Terrarum.ingame!!.actorContainerActive.forEach {
if (it is ActorWBMovable && it.hIntTilewiseHitbox.intersects(mousePoint))
return false
}

View File

@@ -1,15 +1,12 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
import org.luaj.vm2.*
import org.luaj.vm2.lib.OneArgFunction
import org.luaj.vm2.lib.TwoArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil.VDPath
import java.io.*
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaError
import org.luaj.vm2.LuaValue
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
@@ -27,7 +24,7 @@ import java.util.*
*/
internal class Filesystem(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
init {
/*init {
// load things. WARNING: THIS IS MANUAL!
globals["fs"] = LuaValue.tableOf()
globals["fs"]["list"] = ListFiles(computer) // CC compliant
@@ -45,7 +42,7 @@ internal class Filesystem(globals: Globals, computer: net.torvald.terrarum.modul
globals["fs"]["parent"] = GetParentDir(computer)
// fs.dofile defined in BOOT
// fs.fetchText defined in ROMLIB
}
}*/
companion object {
val sysCharset = Charset.forName("CP437")
@@ -136,6 +133,8 @@ internal class Filesystem(globals: Globals, computer: net.torvald.terrarum.modul
}
} // end of Companion Object
/*
/**
* @param cname == UUID of the drive
*
@@ -509,5 +508,5 @@ internal class Filesystem(globals: Globals, computer: net.torvald.terrarum.modul
return if (scanner.hasNextLine()) LuaValue.valueOf(scanner.nextLine())
else LuaValue.NIL
}
}
}*/
}

View File

@@ -1,213 +0,0 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
import java.io.*
import java.util.*
/**
* ByteArray that can hold larger than 2 GiB of Data.
*
* Works kind of like Bank Switching of old game console's cartridges which does same thing.
*
* Created by Minjaesong on 2017-04-12.
*/
class ByteArray64(val size: Long) {
companion object {
val bankSize: Int = 8192
}
internal val __data: Array<ByteArray>
init {
if (size < 0)
throw IllegalArgumentException("Invalid array size!")
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
__data = Array<ByteArray>(
requiredBanks,
{ bankIndex ->
kotlin.ByteArray(
if (bankIndex == requiredBanks - 1)
size.toBankOffset()
else
bankSize,
{ 0.toByte() }
)
}
)
}
private fun Long.toBankNumber(): Int = (this / bankSize).toInt()
private fun Long.toBankOffset(): Int = (this % bankSize).toInt()
operator fun set(index: Long, value: Byte) {
if (index < 0 || index >= size)
throw ArrayIndexOutOfBoundsException("size $size, index $index")
__data[index.toBankNumber()][index.toBankOffset()] = value
}
operator fun get(index: Long): Byte {
if (index < 0 || index >= size)
throw ArrayIndexOutOfBoundsException("size $size, index $index")
return __data[index.toBankNumber()][index.toBankOffset()]
}
operator fun iterator(): ByteIterator {
return object : ByteIterator() {
var iterationCounter = 0L
override fun nextByte(): Byte {
iterationCounter += 1
return this@ByteArray64[iterationCounter - 1]
}
override fun hasNext() = iterationCounter < this@ByteArray64.size
}
}
fun iteratorChoppedToInt(): IntIterator {
return object : IntIterator() {
var iterationCounter = 0L
val iteratorSize = 1 + ((this@ByteArray64.size - 1) / 4).toInt()
override fun nextInt(): Int {
var byteCounter = iterationCounter * 4L
var int = 0
(0..3).forEach {
if (byteCounter + it < this@ByteArray64.size) {
int += this@ByteArray64[byteCounter + it].toInt() shl (it * 8)
}
else {
int += 0 shl (it * 8)
}
}
iterationCounter += 1
return int
}
override fun hasNext() = iterationCounter < iteratorSize
}
}
fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) }
fun forEachBanks(consumer: (ByteArray) -> Unit) = __data.forEach(consumer)
fun sliceArray64(range: LongRange): ByteArray64 {
val newarr = ByteArray64(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index]
}
return newarr
}
fun sliceArray(range: IntRange): ByteArray {
val newarr = ByteArray(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index.toLong()]
}
return newarr
}
fun toByteArray(): ByteArray {
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
throw TypeCastException("Impossible cast; too large to fit")
return ByteArray(this.size.toInt(), { this[it.toLong()] })
}
fun writeToFile(file: File) {
var fos = FileOutputStream(file, false)
fos.write(__data[0])
fos.flush()
fos.close()
if (__data.size > 1) {
fos = FileOutputStream(file, true)
for (i in 1..__data.lastIndex) {
fos.write(__data[i])
fos.flush()
}
fos.close()
}
}
}
open class ByteArray64InputStream(val byteArray64: ByteArray64): InputStream() {
protected open var readCounter = 0L
override fun read(): Int {
readCounter += 1
return try {
byteArray64[readCounter - 1].toUint()
}
catch (e: ArrayIndexOutOfBoundsException) {
-1
}
}
}
/** Static ByteArray OutputStream. Less leeway, more stable. */
open class ByteArray64OutputStream(val byteArray64: ByteArray64): OutputStream() {
protected open var writeCounter = 0L
override fun write(b: Int) {
try {
writeCounter += 1
byteArray64[writeCounter - 1] = b.toByte()
}
catch (e: ArrayIndexOutOfBoundsException) {
throw IOException(e)
}
}
}
/** Just like Java's ByteArrayOutputStream, except DON'T TRY TO GROW THE BUFFER */
open class ByteArray64GrowableOutputStream(val size: Long = ByteArray64.bankSize.toLong()): OutputStream() {
protected open var buf = ByteArray64(size)
protected open var count = 0L
override fun write(b: Int) {
ensureCapacity(count + 1)
buf[count] = b.toByte()
count += 1
}
private fun ensureCapacity(minCapacity: Long) {
// overflow-conscious code
if (minCapacity - buf.size > 0)
grow(minCapacity)
}
private fun grow(minCapacity: Long) {
// overflow-conscious code
val oldCapacity = buf.size
var newCapacity = oldCapacity shl 1
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity
// double the capacity
val newBuffer = ByteArray64(buf.size * 2)
buf.__data.forEachIndexed { index, bytes ->
System.arraycopy(
buf.__data[index], 0,
newBuffer.__data[index], 0, buf.__data.size
)
}
buf = newBuffer
System.gc()
}
/** Unlike Java's, this does NOT create a copy of the internal buffer; this just returns its internal. */
@Synchronized
fun toByteArray64(): ByteArray64 {
return buf
}
}

View File

@@ -1,203 +0,0 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
import net.torvald.terrarum.serialise.toLittleInt
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
/**
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
*
* Created by minjaesong on 2017-11-17.
*/
class DiskSkimmer(private val diskFile: File) {
/**
* EntryID to Offset.
*
* Offset is where the header begins, so first 4 bytes are exactly the same as the EntryID.
*/
val entryToOffsetTable = HashMap<EntryID, Long>()
init {
val fis = FileInputStream(diskFile)
var currentPosition = fis.skip(47) // skip disk header
fun skipRead(bytes: Long) {
currentPosition += fis.skip(bytes)
}
/**
* Reads a byte and adds up the position var
*/
fun readByte(): Byte {
currentPosition++
val read = fis.read()
if (read < 0) throw InternalError("Unexpectedly reached EOF")
return read.toByte()
}
/**
* Reads specific bytes to the buffer and adds up the position var
*/
fun readBytes(buffer: ByteArray): Int {
val readStatus = fis.read(buffer)
currentPosition += readStatus
return readStatus
}
fun readIntBig(): Int {
val buffer = ByteArray(4)
val readStatus = readBytes(buffer)
if (readStatus != 4) throw InternalError("Unexpected error -- EOF reached? (expected 4, got $readStatus)")
return buffer.toIntBig()
}
fun readInt48(): Long {
val buffer = ByteArray(6)
val readStatus = readBytes(buffer)
if (readStatus != 6) throw InternalError("Unexpected error -- EOF reached? (expected 6, got $readStatus)")
return buffer.toInt48()
}
while (true) {
val entryID = readIntBig()
// footer
if (entryID == 0xFEFEFEFE.toInt()) break
// fill up table
entryToOffsetTable[entryID] = currentPosition
skipRead(4) // skip entryID of parent
val entryType = readByte()
skipRead(256 + 6 + 6 + 4) // skips rest of the header
// figure out the entry size so that we can skip
val entrySize: Long = when(entryType) {
0x01.toByte() -> readInt48()
0x11.toByte() -> readInt48() + 6 // size of compressed payload + 6 (header elem for uncompressed size)
0x02.toByte() -> readIntBig().shl(16).toLong() * 4 - 2 // #entris is 2 bytes, we read 4 bytes, so we subtract 2
0x03.toByte() -> 4 // symlink
else -> throw InternalError("Unknown entry type: ${entryType.toUint()}")
}
skipRead(entrySize) // skips rest of the entry's actual contents
}
}
/**
* Using entryToOffsetTable, composes DiskEntry on the fly upon request.
* @return DiskEntry if the entry exists on the disk, `null` otherwise.
*/
fun requestFile(entryID: EntryID): DiskEntry? {
// FIXME untested
entryToOffsetTable[entryID].let { offset ->
if (offset == null)
return null
else {
val fis = FileInputStream(diskFile)
fis.skip(offset + 4) // get to the EntryHeader's parent directory area
val parent = fis.read(4).toLittleInt()
val fileFlag = fis.read(1)[0]
val filename = fis.read(256)
val creationTime = fis.read(6).toInt48()
val modifyTime = fis.read(6).toInt48()
val skip_crc = fis.read(4)
// get entry size // TODO future me, does this kind of comment helpful or redundant?
val entrySize = when (fileFlag) {
DiskEntry.NORMAL_FILE -> {
fis.read(6).toInt48()
}
DiskEntry.DIRECTORY -> {
fis.read(2).toUint16().toLong()
}
DiskEntry.SYMLINK -> 4L
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
}
val entryContent = when (fileFlag) {
DiskEntry.NORMAL_FILE -> {
val byteArray = ByteArray64(entrySize)
// read one byte at a time
for (c in 0L until entrySize) {
byteArray[c] = fis.read().toByte()
}
EntryFile(byteArray)
}
DiskEntry.DIRECTORY -> {
val dirContents = ArrayList<EntryID>()
// read 4 bytes at a time
val bytesBuffer4 = ByteArray(4)
for (c in 0L until entrySize) {
fis.read(bytesBuffer4)
dirContents.add(bytesBuffer4.toIntBig())
}
EntryDirectory(dirContents)
}
DiskEntry.SYMLINK -> {
val target = fis.read(4).toIntBig()
EntrySymlink(target)
}
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
}
return DiskEntry(entryID, parent, filename, creationTime, modifyTime, entryContent)
}
}
}
companion object {
/** Only use it when you're sure you won't reach EOF; unavailable cells in array will be filled with -1. */
fun InputStream.read(size: Int): ByteArray {
val ba = ByteArray(size)
this.read(ba)
return ba
}
}
private fun ByteArray.toUint16(): Int {
return this[0].toUint().shl(8) or
this[1].toUint()
}
private fun ByteArray.toIntBig(): Int {
return this[0].toUint().shl(24) or
this[1].toUint().shl(16) or
this[2].toUint().shl(8) or
this[3].toUint()
}
private fun ByteArray.toInt48(): Long {
return this[0].toUlong().shl(40) or
this[1].toUlong().shl(32) or
this[2].toUlong().shl(24) or
this[3].toUlong().shl(16) or
this[4].toUlong().shl(8) or
this[5].toUlong()
}
private fun ByteArray.toInt64(): Long {
return this[0].toUlong().shl(56) or
this[1].toUlong().shl(48) or
this[2].toUlong().shl(40) or
this[3].toUlong().shl(32) or
this[4].toUlong().shl(24) or
this[5].toUlong().shl(16) or
this[6].toUlong().shl(8) or
this[7].toUlong()
}
}

View File

@@ -1,113 +0,0 @@
# Terran Virtual Disk Image Format Specification
current specversion number: 0x03
## Changes
### 0x03
- Option to compress file entry
### 0x02
- 48-Bit filesize and timestamp (Max 256 TiB / 8.9 million years)
- 8 Reserved footer
### 0x01
**Note: this version were never released in public**
- Doubly Linked List instead of Singly
## Specs
* File structure
Header
IndexNumber
<entry>
IndexNumber
<entry>
IndexNumber
<entry>
...
Footer
* Order of the indices does not matter. Actual sorting is a job of the application.
* Endianness: Big
## Header
Uint8[4] Magic: TEVd
Int48 Disk size in bytes (max 256 TiB)
Uint8[32] Disk name
Int32 CRC-32
1. create list of arrays that contains CRC
2. put all the CRCs of entries
3. sort the list (here's the catch -- you will treat CRCs as SIGNED integer)
4. for elems on list: update crc with the elem (crc = calculateCRC(crc, elem))
Int8 Version
(Header size: 47 bytes)
## IndexNumber and Contents
<Entry Header>
<Actual Entry>
### Entry Header
Int32 EntryID (random Integer). This act as "jump" position for directory listing.
NOTE: Index 0 must be a root "Directory"; 0xFEFEFEFE is invalid (used as footer marker)
Int32 EntryID of parent directory
Int8 Flag for file or directory or symlink (cannot be negative)
0x01: Normal file, 0x02: Directory list, 0x03: Symlink
0x11: Compressed normal file
Uint8[256] File name in UTF-8
Int48 Creation date in real-life UNIX timestamp
Int48 Last modification date in real-life UNIX timestamp
Int32 CRC-32 of Actual Entry
(Header size: 281 bytes)
### Entry of File (Uncompressed)
Int48 File size in bytes (max 256 TiB)
<Bytes> Actual Contents
(Header size: 6 bytes)
### Entry of File (Compressed)
Int48 Size of compressed payload (max 256 TiB)
Int48 Size of uncompressed file (max 256 TiB)
<Bytes> Actual Contents, DEFLATEd payload
(Header size: 12 bytes)
### Entry of Directory
Uint16 Number of entries (normal files, other directories, symlinks)
<Int32s> Entry listing, contains IndexNumber
(Header size: 2 bytes)
### Entry of Symlink
Int32 Target IndexNumber
(Content size: 4 bytes)
## Footer
Uint8[4] 0xFE 0xFE 0xFE 0xFE (footer marker)
Int8 Disk properties flag 1
0b 7 6 5 4 3 2 1 0
0th bit: Readonly
Int8[7] Reserved, should be filled with zero
<optional footer if present>
Uint8[2] 0xFF 0x19 (EOF mark)

View File

@@ -1,307 +0,0 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
import java.util.zip.CRC32
import kotlin.experimental.and
import kotlin.experimental.or
/**
* Created by minjaesong on 2017-03-31.
*/
typealias EntryID = Int
val specversion = 0x03.toByte()
class VirtualDisk(
/** capacity of 0 makes the disk read-only */
var capacity: Long,
var diskName: ByteArray = ByteArray(NAME_LENGTH),
footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(8) // default to mandatory 8-byte footer
) {
var footerBytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = footer
private set
val entries = HashMap<EntryID, DiskEntry>()
var isReadOnly: Boolean
set(value) { footerBytes[0] = (footerBytes[0] and 0xFE.toByte()) or value.toBit() }
get() = capacity == 0L || (footerBytes.size > 0 && footerBytes[0].and(1) == 1.toByte())
fun getDiskNameString(charset: Charset) = String(diskName, charset)
val root: DiskEntry
get() = entries[0]!!
private fun Boolean.toBit() = if (this) 1.toByte() else 0.toByte()
internal fun __internalSetFooter__(footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) {
footerBytes = footer
}
private fun serializeEntriesOnly(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 {
val bufferList = ArrayList<Byte>() // FIXME this part would take up excessive memory for large files
entries.forEach {
val serialised = it.value.serialize()
serialised.forEach { bufferList.add(it) }
}
val byteArray = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(bufferList.size.toLong())
bufferList.forEachIndexed { index, byte -> byteArray[index.toLong()] = byte }
return byteArray
}
fun serialize(): AppendableByteBuffer {
val entriesBuffer = serializeEntriesOnly()
val buffer = AppendableByteBuffer(HEADER_SIZE + entriesBuffer.size + FOOTER_SIZE + footerBytes.size)
val crc = hashCode().toBigEndian()
buffer.put(MAGIC)
buffer.put(capacity.toInt48())
buffer.put(diskName.forceSize(NAME_LENGTH))
buffer.put(crc)
buffer.put(specversion)
buffer.put(entriesBuffer)
buffer.put(FOOTER_START_MARK)
buffer.put(footerBytes)
buffer.put(EOF_MARK)
return buffer
}
override fun hashCode(): Int {
val crcList = IntArray(entries.size)
var crcListAppendCursor = 0
entries.forEach { _, u ->
crcList[crcListAppendCursor] = u.hashCode()
crcListAppendCursor++
}
crcList.sort()
val crc = CRC32()
crcList.forEach { crc.update(it) }
return crc.value.toInt()
}
/** Expected size of the virtual disk */
val usedBytes: Long
get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE
fun generateUniqueID(): Int {
var id: Int
do {
id = Random().nextInt()
} while (null != entries[id] || id == FOOTER_MARKER)
return id
}
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})"
companion object {
val HEADER_SIZE = 47L // according to the spec
val FOOTER_SIZE = 6L // footer mark + EOF
val NAME_LENGTH = 32
val MAGIC = "TEVd".toByteArray()
val FOOTER_MARKER = 0xFEFEFEFE.toInt()
val FOOTER_START_MARK = FOOTER_MARKER.toBigEndian()
val EOF_MARK = byteArrayOf(0xFF.toByte(), 0x19.toByte())
}
}
class DiskEntry(
// header
var entryID: EntryID,
var parentEntryID: EntryID,
var filename: ByteArray = ByteArray(NAME_LENGTH),
var creationDate: Long,
var modificationDate: Long,
// content
val contents: DiskEntryContent
) {
fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset)
val serialisedSize: Long
get() = contents.getSizeEntry() + HEADER_SIZE
companion object {
val HEADER_SIZE = 281L // according to the spec
val ROOTNAME = "(root)"
val NAME_LENGTH = 256
val NORMAL_FILE = 1.toByte()
val DIRECTORY = 2.toByte()
val SYMLINK = 3.toByte()
val COMPRESSED_FILE = 0x11.toByte()
private fun DiskEntryContent.getTypeFlag() =
if (this is EntryFile) NORMAL_FILE
else if (this is EntryDirectory) DIRECTORY
else if (this is EntrySymlink) SYMLINK
else 0 // NULL
fun getTypeString(entry: DiskEntryContent) = when(entry.getTypeFlag()) {
NORMAL_FILE -> "File"
DIRECTORY -> "Directory"
SYMLINK -> "Symbolic Link"
else -> "(unknown type)"
}
}
fun serialize(): AppendableByteBuffer {
val serialisedContents = contents.serialize()
val buffer = AppendableByteBuffer(HEADER_SIZE + serialisedContents.size)
buffer.put(entryID.toBigEndian())
buffer.put(parentEntryID.toBigEndian())
buffer.put(contents.getTypeFlag())
buffer.put(filename.forceSize(NAME_LENGTH))
buffer.put(creationDate.toInt48())
buffer.put(modificationDate.toInt48())
buffer.put(this.hashCode().toBigEndian())
buffer.put(serialisedContents.array)
return buffer
}
override fun hashCode() = contents.serialize().getCRC32()
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
override fun toString() = "DiskEntry(name: ${getFilenameString(Charsets.UTF_8)}, index: $entryID, type: ${contents.getTypeFlag()}, crc: ${hashCode().toHex()})"
}
fun ByteArray.forceSize(size: Int): ByteArray {
return ByteArray(size, { if (it < this.size) this[it] else 0.toByte() })
}
interface DiskEntryContent {
fun serialize(): AppendableByteBuffer
fun getSizePure(): Long
fun getSizeEntry(): Long
}
/**
* Do not retrieve bytes directly from this! Use VDUtil.retrieveFile(DiskEntry)
* And besides, the bytes could be compressed.
*/
open class EntryFile(internal var bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : DiskEntryContent {
override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 6
/** Create new blank file */
constructor(size: Long): this(net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size))
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(getSizePure().toInt48())
buffer.put(bytes)
return buffer
}
}
class EntryFileCompressed(internal var uncompressedSize: Long, bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : EntryFile(bytes) {
override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 12
/* No new blank file for the compressed */
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(getSizePure().toInt48())
buffer.put(uncompressedSize.toInt48())
buffer.put(bytes)
return buffer
}
}
class EntryDirectory(private val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
override fun getSizePure() = entries.size * 4L
override fun getSizeEntry() = getSizePure() + 2
private fun checkCapacity(toAdd: Int = 1) {
if (entries.size + toAdd > 65535)
throw IOException("Directory entries limit exceeded.")
}
fun add(entryID: EntryID) {
checkCapacity()
entries.add(entryID)
}
fun remove(entryID: EntryID) {
entries.remove(entryID)
}
fun contains(entryID: EntryID) = entries.contains(entryID)
fun forEach(consumer: (EntryID) -> Unit) = entries.forEach(consumer)
val entryCount: Int
get() = entries.size
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(entries.size.toShort().toBigEndian())
entries.forEach { indexNumber -> buffer.put(indexNumber.toBigEndian()) }
return buffer
}
companion object {
val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4L
}
}
class EntrySymlink(val target: EntryID) : DiskEntryContent {
override fun getSizePure() = 4L
override fun getSizeEntry() = 4L
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(4)
return buffer.put(target.toBigEndian())
}
}
fun Int.toHex() = this.toLong().and(0xFFFFFFFF).toString(16).padStart(8, '0').toUpperCase()
fun Int.toBigEndian(): ByteArray {
return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() })
}
fun Long.toInt48(): ByteArray {
return ByteArray(6, { this.ushr(40 - (8 * it)).toByte() })
}
fun Short.toBigEndian(): ByteArray {
return byteArrayOf(
this.div(256).toByte(),
this.toByte()
)
}
fun AppendableByteBuffer.getCRC32(): Int {
val crc = CRC32()
this.array.forEachInt32 { crc.update(it) }
return crc.value.toInt()
}
class AppendableByteBuffer(val size: Long) {
val array = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size)
private var offset = 0L
fun put(byteArray64: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64): AppendableByteBuffer {
// it's slow but works
// can't do system.arrayCopy directly
byteArray64.forEach { put(it) }
return this
}
fun put(byteArray: ByteArray): AppendableByteBuffer {
byteArray.forEach { put(it) }
return this
}
fun put(byte: Byte): AppendableByteBuffer {
array[offset] = byte
offset += 1
return this
}
fun forEach(consumer: (Byte) -> Unit) = array.forEach(consumer)
}

View File

@@ -1,107 +0,0 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.finder
import java.awt.BorderLayout
import java.awt.GridLayout
import javax.swing.*
/**
* Created by SKYHi14 on 2017-04-01.
*/
object Popups {
val okCancel = arrayOf("OK", "Cancel")
}
class OptionDiskNameAndCap {
val name = JTextField(11)
val capacity = JSpinner(SpinnerNumberModel(
368640L.toJavaLong(),
0L.toJavaLong(),
(1L shl 38).toJavaLong(),
1L.toJavaLong()
)) // default 360 KiB, MAX 256 GiB
val mainPanel = JPanel()
val settingPanel = JPanel()
init {
mainPanel.layout = BorderLayout()
settingPanel.layout = GridLayout(2, 2, 2, 0)
//name.text = "Unnamed"
settingPanel.add(JLabel("Name (max 32 bytes)"))
settingPanel.add(name)
settingPanel.add(JLabel("Capacity (bytes)"))
settingPanel.add(capacity)
mainPanel.add(settingPanel, BorderLayout.CENTER)
mainPanel.add(JLabel("Set capacity to 0 to make the disk read-only"), BorderLayout.SOUTH)
}
/**
* returns either JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION
*/
fun showDialog(title: String): Int {
return JOptionPane.showConfirmDialog(null, mainPanel,
title, JOptionPane.OK_CANCEL_OPTION)
}
}
fun kotlin.Long.toJavaLong() = java.lang.Long(this)
class OptionFileNameAndCap {
val name = JTextField(11)
val capacity = JSpinner(SpinnerNumberModel(
4096L.toJavaLong(),
0L.toJavaLong(),
((1L shl 48) - 1L).toJavaLong(),
1L.toJavaLong()
)) // default 360 KiB, MAX 256 TiB
val mainPanel = JPanel()
val settingPanel = JPanel()
init {
mainPanel.layout = BorderLayout()
settingPanel.layout = GridLayout(2, 2, 2, 0)
//name.text = "Unnamed"
settingPanel.add(JLabel("Name (max 32 bytes)"))
settingPanel.add(name)
settingPanel.add(JLabel("Capacity (bytes)"))
settingPanel.add(capacity)
mainPanel.add(settingPanel, BorderLayout.CENTER)
}
/**
* returns either JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION
*/
fun showDialog(title: String): Int {
return JOptionPane.showConfirmDialog(null, mainPanel,
title, JOptionPane.OK_CANCEL_OPTION)
}
}
class OptionSize {
val capacity = JSpinner(SpinnerNumberModel(
368640L.toJavaLong(),
0L.toJavaLong(),
(1L shl 38).toJavaLong(),
1L.toJavaLong()
)) // default 360 KiB, MAX 256 GiB
val settingPanel = JPanel()
init {
settingPanel.add(JLabel("Size (bytes)"))
settingPanel.add(capacity)
}
/**
* returns either JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION
*/
fun showDialog(title: String): Int {
return JOptionPane.showConfirmDialog(null, settingPanel,
title, JOptionPane.OK_CANCEL_OPTION)
}
}

View File

@@ -1,843 +0,0 @@
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.finder
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.nio.charset.Charset
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.logging.Level
import javax.swing.*
import javax.swing.table.AbstractTableModel
import javax.swing.ListSelectionModel
import javax.swing.text.DefaultCaret
/**
* Created by SKYHi14 on 2017-04-01.
*/
class VirtualDiskCracker(val sysCharset: Charset = Charsets.UTF_8) : JFrame() {
private val annoyHackers = true // Jar build settings. Intended for Terrarum proj.
private val PREVIEW_MAX_BYTES = 4L * 1024 // 4 kBytes
private val appName = "TerranVirtualDiskCracker"
private val copyright = "Copyright 2017 Torvald (minjaesong). Distributed under MIT license."
private val magicOpen = "I solemnly swear that I am up to no good."
private val magicSave = "Mischief managed."
private val annoyWhenLaunchMsg = "Type in following to get started:\n$magicOpen"
private val annoyWhenSaveMsg = "Type in following to save:\n$magicSave"
private val panelMain = JPanel()
private val menuBar = JMenuBar()
private val tableFiles: JTable
private val fileDesc = JTextArea()
private val diskInfo = JTextArea()
private val statBar = JLabel("Open a disk or create new to get started")
private var vdisk: VirtualDisk? = null
private var clipboard: DiskEntry? = null
private val labelPath = JLabel("(root)")
private var currentDirectoryEntries: Array<DiskEntry>? = null
private val directoryHierarchy = Stack<EntryID>(); init { directoryHierarchy.push(0) }
private fun gotoSubDirectory(id: EntryID) {
directoryHierarchy.push(id)
labelPath.text = vdisk!!.entries[id]!!.getFilenameString(sysCharset)
selectedFile = null
fileDesc.text = ""
updateDiskInfo()
}
val currentDirectory: EntryID
get() = directoryHierarchy.peek()
val upperDirectory: EntryID
get() = if (directoryHierarchy.lastIndex == 0) 0
else directoryHierarchy[directoryHierarchy.lastIndex - 1]
private fun gotoRoot() {
directoryHierarchy.removeAllElements()
directoryHierarchy.push(0)
selectedFile = null
fileDesc.text = ""
updateDiskInfo()
}
private fun gotoParent() {
if (directoryHierarchy.size > 1)
directoryHierarchy.pop()
selectedFile = null
fileDesc.text = ""
updateDiskInfo()
}
private var selectedFile: EntryID? = null
val tableColumns = arrayOf("Name", "Date Modified", "Size")
val tableParentRecord = arrayOf(arrayOf("..", "", ""))
init {
if (annoyHackers) {
val mantra = JOptionPane.showInputDialog(annoyWhenLaunchMsg)
if (mantra != magicOpen) {
System.exit(1)
}
}
panelMain.layout = BorderLayout()
this.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
tableFiles = JTable(tableParentRecord, tableColumns)
tableFiles.addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
val table = e.source as JTable
val row = table.rowAtPoint(e.point)
selectedFile = if (row > 0)
currentDirectoryEntries!![row - 1].entryID
else
null // clicked ".."
fileDesc.text = if (selectedFile != null) {
getFileInfoText(vdisk!!.entries[selectedFile!!]!!)
}
else
""
fileDesc.caretPosition = 0
// double click
if (e.clickCount == 2) {
if (row == 0) {
gotoParent()
}
else {
val record = currentDirectoryEntries!![row - 1]
if (record.contents is EntryDirectory) {
gotoSubDirectory(record.entryID)
}
}
}
}
})
tableFiles.selectionModel = object : DefaultListSelectionModel() {
init { selectionMode = ListSelectionModel.SINGLE_SELECTION }
override fun clearSelection() { } // required!
override fun removeSelectionInterval(index0: Int, index1: Int) { } // required!
override fun fireValueChanged(isAdjusting: Boolean) { } // required!
}
tableFiles.model = object : AbstractTableModel() {
override fun getRowCount(): Int {
return if (vdisk != null)
1 + (currentDirectoryEntries?.size ?: 0)
else 1
}
override fun getColumnCount() = tableColumns.size
override fun getColumnName(column: Int) = tableColumns[column]
override fun getValueAt(rowIndex: Int, columnIndex: Int): Any {
if (rowIndex == 0) {
return tableParentRecord[0][columnIndex]
}
else {
if (vdisk != null) {
val entry = currentDirectoryEntries!![rowIndex - 1]
return when(columnIndex) {
0 -> entry.getFilenameString(sysCharset)
1 -> Instant.ofEpochSecond(entry.modificationDate).
atZone(TimeZone.getDefault().toZoneId()).
format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
2 -> entry.getEffectiveSize()
else -> ""
}
}
else {
return ""
}
}
}
}
val menuFile = JMenu("File")
menuFile.mnemonic = KeyEvent.VK_F
menuFile.add("New Disk…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
try {
val makeNewDisk: Boolean
if (vdisk != null) {
makeNewDisk = confirmedDiscard()
}
else {
makeNewDisk = true
}
if (makeNewDisk) {
// inquire new size
val dialogBox = OptionDiskNameAndCap()
val confirmNew = JOptionPane.OK_OPTION == dialogBox.showDialog("Set Property of New Disk")
if (confirmNew) {
vdisk = VDUtil.createNewDisk(
(dialogBox.capacity.value as Long).toLong(),
dialogBox.name.text,
sysCharset
)
gotoRoot()
updateDiskInfo()
setStat("Disk created")
}
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
})
menuFile.add("Open Disk…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
val makeNewDisk: Boolean
if (vdisk != null) {
makeNewDisk = confirmedDiscard()
}
else {
makeNewDisk = true
}
if (makeNewDisk) {
val fileChooser = JFileChooser()
fileChooser.showOpenDialog(null)
if (fileChooser.selectedFile != null) {
try {
vdisk = VDUtil.readDiskArchive(fileChooser.selectedFile, Level.WARNING, { popupWarning(it) }, sysCharset)
if (vdisk != null) {
gotoRoot()
updateDiskInfo()
setStat("Disk loaded")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
}
})
menuFile.addSeparator()
menuFile.add("Save Disk as…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
if (annoyHackers) {
val mantra = JOptionPane.showInputDialog(annoyWhenSaveMsg)
if (mantra != magicSave) {
popupError("Nope!")
return
}
}
val fileChooser = JFileChooser()
fileChooser.showSaveDialog(null)
if (fileChooser.selectedFile != null) {
try {
VDUtil.dumpToRealMachine(vdisk!!, fileChooser.selectedFile)
setStat("Disk saved")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
}
})
menuBar.add(menuFile)
val menuEdit = JMenu("Edit")
menuEdit.mnemonic = KeyEvent.VK_E
menuEdit.add("New File…").addMouseListener(object: MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val dialogBox = OptionFileNameAndCap()
val confirmNew = JOptionPane.OK_OPTION == dialogBox.showDialog("Set Property of New File")
if (confirmNew) {
if (VDUtil.nameExists(vdisk!!, dialogBox.name.text, currentDirectory, sysCharset)) {
popupError("The name already exists")
}
else {
VDUtil.createNewBlankFile(
vdisk!!,
currentDirectory,
(dialogBox.capacity.value as Long).toLong(),
dialogBox.name.text,
sysCharset
)
updateDiskInfo()
setStat("File created")
}
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.add("New Directory…").addMouseListener(object: MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
val newname = JOptionPane.showInputDialog("Enter a new directory name:")
if (newname != null) {
try {
if (VDUtil.nameExists(vdisk!!, newname, currentDirectory, sysCharset)) {
popupError("The name already exists")
}
else {
VDUtil.addDir(vdisk!!, currentDirectory, newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
updateDiskInfo()
setStat("Directory created")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
}
})
menuEdit.addSeparator()
menuEdit.add("Cut").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
// copy
clipboard = vdisk!!.entries[selectedFile]
// delete
if (vdisk != null && selectedFile != null) {
try {
VDUtil.deleteFile(vdisk!!, selectedFile!!)
updateDiskInfo()
setStat("File deleted")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.add("Copy").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
clipboard = vdisk!!.entries[selectedFile]
setStat("File copied")
}
})
menuEdit.add("Paste").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
fun paste1(newname: ByteArray) {
try {
VDUtil.addFile(vdisk!!, currentDirectory, DiskEntry(// clone
vdisk!!.generateUniqueID(),
currentDirectory,
newname,
clipboard!!.creationDate,
clipboard!!.modificationDate,
clipboard!!.contents
))
updateDiskInfo()
setStat("File pasted")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
if (clipboard != null && vdisk != null) {
// check name collision. If it is, ask for new one
if (VDUtil.nameExists(vdisk!!, clipboard!!.getFilenameString(sysCharset), currentDirectory, sysCharset)) {
val newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
if (newname != null) {
paste1(newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
}
}
else {
paste1(clipboard!!.filename)
}
}
}
})
menuEdit.add("Paste as Symbolic Link").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
fun pasteSymbolic(newname: ByteArray) {
try {
// check if the original file is there in the first place
if (vdisk!!.entries[clipboard!!.entryID] != null) {
val entrySymlink = EntrySymlink(clipboard!!.entryID)
VDUtil.addFile(vdisk!!, currentDirectory, DiskEntry(
vdisk!!.generateUniqueID(),
currentDirectory,
newname,
VDUtil.currentUnixtime,
VDUtil.currentUnixtime,
entrySymlink
))
updateDiskInfo()
setStat("Symbolic link created")
}
else {
popupError("The orignal file is gone")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
if (clipboard != null && vdisk != null) {
// check name collision. If it is, ask for new one
if (VDUtil.nameExists(vdisk!!, clipboard!!.getFilenameString(sysCharset), currentDirectory, sysCharset)) {
val newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
if (newname != null) {
pasteSymbolic(newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
}
}
else {
pasteSymbolic(clipboard!!.filename)
}
}
}
})
menuEdit.add("Delete").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null && selectedFile != null) {
try {
VDUtil.deleteFile(vdisk!!, selectedFile!!)
updateDiskInfo()
setStat("File deleted")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.add("Rename…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (selectedFile != null) {
try {
val newname = JOptionPane.showInputDialog("Enter a new name:")
if (newname != null) {
if (VDUtil.nameExists(vdisk!!, newname, currentDirectory, sysCharset)) {
popupError("The name already exists")
}
else {
VDUtil.renameFile(vdisk!!, selectedFile!!, newname, sysCharset)
updateDiskInfo()
setStat("File renamed")
}
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.add("Look Clipboard").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
popupMessage(if (clipboard != null)
"${clipboard ?: "(bug found)"}"
else "(nothing)", "Clipboard"
)
}
})
menuEdit.addSeparator()
menuEdit.add("Import…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
val fileChooser = JFileChooser()
fileChooser.fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES
fileChooser.isMultiSelectionEnabled = true
fileChooser.showOpenDialog(null)
if (fileChooser.selectedFiles.isNotEmpty()) {
try {
fileChooser.selectedFiles.forEach {
if (!it.isDirectory) {
val entry = VDUtil.importFile(it, vdisk!!.generateUniqueID(), sysCharset)
val newname: String?
if (VDUtil.nameExists(vdisk!!, entry.filename, currentDirectory)) {
newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
}
else {
newname = entry.getFilenameString(sysCharset)
}
if (newname != null) {
entry.filename = newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset)
VDUtil.addFile(vdisk!!, currentDirectory, entry)
}
}
else {
val newname: String?
if (VDUtil.nameExists(vdisk!!, it.name.toEntryName(DiskEntry.NAME_LENGTH, sysCharset), currentDirectory)) {
newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
}
else {
newname = it.name
}
if (newname != null) {
VDUtil.importDirRecurse(vdisk!!, it, currentDirectory, sysCharset, newname)
}
}
}
updateDiskInfo()
setStat("File added")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
fileChooser.isMultiSelectionEnabled = false
}
}
})
menuEdit.add("Export…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
val file = vdisk!!.entries[selectedFile ?: currentDirectory]!!
val fileChooser = JFileChooser()
fileChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
fileChooser.isMultiSelectionEnabled = false
fileChooser.showSaveDialog(null)
if (fileChooser.selectedFile != null) {
try {
val file = VDUtil.resolveIfSymlink(vdisk!!, file.entryID)
if (file.contents is EntryFile) {
VDUtil.exportFile(file.contents, fileChooser.selectedFile)
setStat("File exported")
}
else {
VDUtil.exportDirRecurse(vdisk!!, file.entryID, fileChooser.selectedFile, sysCharset)
setStat("Files exported")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
}
})
menuEdit.addSeparator()
menuEdit.add("Rename Disk…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val newname = JOptionPane.showInputDialog("Enter a new disk name:")
if (newname != null) {
vdisk!!.diskName = newname.toEntryName(VirtualDisk.NAME_LENGTH, sysCharset)
updateDiskInfo()
setStat("Disk renamed")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.add("Resize Disk…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val dialog = OptionSize()
val confirmed = dialog.showDialog("Input") == JOptionPane.OK_OPTION
if (confirmed) {
vdisk!!.capacity = (dialog.capacity.value as Long).toLong()
updateDiskInfo()
setStat("Disk resized")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.addSeparator()
menuEdit.add("Set/Unset Write Protection").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
vdisk!!.isReadOnly = vdisk!!.isReadOnly.not()
updateDiskInfo()
setStat("Disk write protection ${if (vdisk!!.isReadOnly) "" else "dis"}engaged")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuBar.add(menuEdit)
val menuManage = JMenu("Manage")
menuManage.add("Report Orphans…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val reports = VDUtil.gcSearchOrphan(vdisk!!)
val orphansCount = reports.size
val orphansSize = reports.map { vdisk!!.entries[it]!!.contents.getSizeEntry() }.sum()
val message = "Orphans count: $orphansCount\n" +
"Size: ${orphansSize.bytes()}"
popupMessage(message, "Orphans Report")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuManage.add("Report Phantoms…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val reports = VDUtil.gcSearchPhantomBaby(vdisk!!)
val phantomsSize = reports.size
val message = "Phantoms count: $phantomsSize"
popupMessage(message, "Phantoms Report")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuManage.addSeparator()
menuManage.add("Remove Orphans").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val oldSize = vdisk!!.usedBytes
VDUtil.gcDumpOrphans(vdisk!!)
val newSize = vdisk!!.usedBytes
popupMessage("Saved ${(oldSize - newSize).bytes()}", "GC Report")
updateDiskInfo()
setStat("Orphan nodes removed")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuManage.add("Full Garbage Collect").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val oldSize = vdisk!!.usedBytes
VDUtil.gcDumpAll(vdisk!!)
val newSize = vdisk!!.usedBytes
popupMessage("Saved ${(oldSize - newSize).bytes()}", "GC Report")
updateDiskInfo()
setStat("Orphan nodes and null directory pointers removed")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuBar.add(menuManage)
val menuAbout = JMenu("About")
menuAbout.addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
popupMessage(copyright, "Copyright")
}
})
menuBar.add(menuAbout)
diskInfo.highlighter = null
diskInfo.text = "(Disk not loaded)"
diskInfo.preferredSize = Dimension(-1, 60)
fileDesc.highlighter = null
fileDesc.text = ""
fileDesc.caret.isVisible = false
(fileDesc.caret as DefaultCaret).updatePolicy = DefaultCaret.NEVER_UPDATE
val fileDescScroll = JScrollPane(fileDesc)
val tableFilesScroll = JScrollPane(tableFiles)
tableFilesScroll.size = Dimension(200, -1)
val panelFinder = JPanel(BorderLayout())
panelFinder.add(labelPath, BorderLayout.NORTH)
panelFinder.add(tableFilesScroll, BorderLayout.CENTER)
val panelFileDesc = JPanel(BorderLayout())
panelFileDesc.add(JLabel("Entry Information"), BorderLayout.NORTH)
panelFileDesc.add(fileDescScroll, BorderLayout.CENTER)
val filesSplit = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panelFinder, panelFileDesc)
filesSplit.resizeWeight = 0.714285
val panelDiskOp = JPanel(BorderLayout(2, 2))
panelDiskOp.add(filesSplit, BorderLayout.CENTER)
panelDiskOp.add(diskInfo, BorderLayout.SOUTH)
panelMain.add(menuBar, BorderLayout.NORTH)
panelMain.add(panelDiskOp, BorderLayout.CENTER)
panelMain.add(statBar, BorderLayout.SOUTH)
this.title = appName
this.add(panelMain)
this.setSize(700, 700)
this.isVisible = true
}
private fun confirmedDiscard() = 0 == JOptionPane.showOptionDialog(
null, // parent
"Any changes to current disk will be discarded. Continue?",
"Confirm Discard", // window title
JOptionPane.DEFAULT_OPTION, // option type
JOptionPane.WARNING_MESSAGE, // message type
null, // icon
Popups.okCancel, // options (provided by JOptionPane.OK_CANCEL_OPTION in this case)
Popups.okCancel[1] // default selection
)
private fun popupMessage(message: String, title: String = "") {
JOptionPane.showOptionDialog(
null,
message,
title,
JOptionPane.DEFAULT_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null, null, null
)
}
private fun popupError(message: String, title: String = "Uh oh…") {
JOptionPane.showOptionDialog(
null,
message,
title,
JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE,
null, null, null
)
}
private fun popupWarning(message: String, title: String = "Careful…") {
JOptionPane.showOptionDialog(
null,
message,
title,
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE,
null, null, null
)
}
private fun updateCurrentDirectory() {
currentDirectoryEntries = VDUtil.getDirectoryEntries(vdisk!!, currentDirectory)
}
private fun updateDiskInfo() {
val sb = StringBuilder()
directoryHierarchy.forEach {
sb.append(vdisk!!.entries[it]!!.getFilenameString(sysCharset))
sb.append('/')
}
sb.dropLast(1)
labelPath.text = sb.toString()
diskInfo.text = if (vdisk == null) "(Disk not loaded)" else getDiskInfoText(vdisk!!)
tableFiles.revalidate()
tableFiles.repaint()
updateCurrentDirectory()
}
private fun getDiskInfoText(disk: VirtualDisk): String {
return """Name: ${String(disk.diskName, sysCharset)}
Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free)
Write protected: ${disk.isReadOnly.toEnglish()}"""
}
private fun Boolean.toEnglish() = if (this) "Yes" else "No"
private fun getFileInfoText(file: DiskEntry): String {
return """Name: ${file.getFilenameString(sysCharset)}
Size: ${file.getEffectiveSize()}
Type: ${DiskEntry.getTypeString(file.contents)}
CRC: ${file.hashCode().toHex()}
EntryID: ${file.entryID}
ParentID: ${file.parentEntryID}""" + if (file.contents is EntryFile) """
Contents:
${String(file.contents.bytes.sliceArray64(0L..minOf(PREVIEW_MAX_BYTES, file.contents.bytes.size) - 1).toByteArray(), sysCharset)}""" else ""
}
private fun Long.bytes() = if (this == 1L) "1 byte" else "$this bytes"
private fun Int.entries() = if (this == 1) "1 entry" else "$this entries"
private fun DiskEntry.getEffectiveSize() = if (this.contents is EntryFile)
this.contents.getSizePure().bytes()
else if (this.contents is EntryDirectory)
this.contents.entryCount.entries()
else if (this.contents is EntrySymlink)
"(symlink)"
else
"n/a"
private fun setStat(message: String) {
statBar.text = message
}
}
fun main(args: Array<String>) {
VirtualDiskCracker(Charset.forName("CP437"))
}

View File

@@ -1,11 +1,15 @@
package net.torvald.terrarum.serialise
import com.badlogic.gdx.Gdx
import com.google.gson.Gson
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskEntry
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.itemproperties.GameItem
import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
import net.torvald.terrarum.roundInt
import java.io.File
import java.nio.charset.Charset
@@ -18,43 +22,116 @@ object SavegameWriter {
private val charset = Charset.forName("UTF-8")
private lateinit var playerName: String
operator fun invoke(): Boolean {
val diskImage = generateDiskImage(null)
playerName = "${Terrarum.ingame!!.actorGamer!!.actorValue[AVKey.NAME]}"
if (playerName.isEmpty()) playerName = "Test subject ${Math.random().times(0x7FFFFFFF).roundInt()}"
try {
val diskImage = generateNewDiskImage()
val outFile = File("${AppLoader.defaultSaveDir}/$playerName")
VDUtil.dumpToRealMachine(diskImage, outFile)
return true
}
catch (e: Throwable) {
e.printStackTrace()
}
return false
}
private fun generateDiskImage(oldDiskFile: File?): VirtualDisk {
val disk = VDUtil.createNewDisk(0x7FFFFFFFFFFFFFFFL, "TerrarumSave", charset)
val oldDiskSkimmer = oldDiskFile?.let { DiskSkimmer(oldDiskFile) }
val ROOT = disk.root.entryID
fun generateNewDiskImage(): VirtualDisk {
val creationDate = System.currentTimeMillis() / 1000L
val ingame = Terrarum.ingame!!
val gameworld = ingame.world
val player = ingame.actorGamer!!
val disk = VDUtil.createNewDisk(0x7FFFFFFFFFFFFFFFL, "Tesv-$playerName", charset)
val ROOT = disk.root.entryID
// serialise current world (stage)
val world = WriteLayerDataZip() // filename can be anything that is "tmp_world[n]" where [n] is any number
val worldFile = VDUtil.importFile(world!!, gameworld.worldIndex, charset)
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")
}
// add current world (stage) to the disk
VDUtil.addFile(disk, ROOT, worldFile)
VDUtil.registerFile(disk, DiskEntry(
gameworld.worldIndex, ROOT,
"world${gameworld.worldIndex}".toByteArray(charset),
creationDate, creationDate,
EntryFile(worldBytes)
))
// put other worlds (stages) to the disk (without loading whole oldDiskFile onto the disk)
oldDiskSkimmer?.let {
// skim-and-write other worlds
for (c in 1..ingame.gameworldCount) {
if (c != gameworld.worldIndex) {
val oldWorldFile = oldDiskSkimmer.requestFile(c)
VDUtil.addFile(disk, ROOT, oldWorldFile!!)
}
}
}
// TODO world[n] is done, needs whole other things
// worldinfo0..3
val worldinfoBytes = WriteWorldInfo(gameworld)
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(
gameworld.worldIndex, ROOT,
it.referenceID!!.toString(16).toUpperCase().toByteArray(charset),
creationDate, creationDate,
EntryFile(serialiseActor(it))
))
}
ingame.actorContainerInactive.forEach {
VDUtil.registerFile(disk, DiskEntry(
gameworld.worldIndex, ROOT,
it.referenceID!!.toString(16).toUpperCase().toByteArray(charset),
creationDate, creationDate,
EntryFile(serialiseActor(it))
))
}
// items
ItemCodex.dynamicItemDescription.forEach { dynamicID, item ->
VDUtil.registerFile(disk, DiskEntry(
gameworld.worldIndex, ROOT,
dynamicID.toString(16).toUpperCase().toByteArray(charset),
creationDate, creationDate,
EntryFile(serialiseItem(item))
))
}
System.gc()
return disk
}
fun modifyExistingSave(savefile: File): VirtualDisk {
TODO()
}
private fun serialiseActor(a: Actor): ByteArray64 {
val gson = Gson().toJsonTree(a).toString().toByteArray(charset)
return ByteArray64.fromByteArray(gson)
}
private fun serialiseItem(i: GameItem): ByteArray64 {
val gson = Gson().toJsonTree(i).toString().toByteArray(charset)
return ByteArray64.fromByteArray(gson)
}
}

View File

@@ -1,11 +1,9 @@
package net.torvald.terrarum.serialise
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.Terrarum
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.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream
@@ -48,14 +46,16 @@ internal object WriteLayerDataZip {
/**
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
* @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(): File? {
val world = (Terrarum.ingame!!.world)
internal operator fun invoke(world: GameWorld): ByteArray64? {
//val sanitisedPath = path.replace('\\', '/').removePrefix("/").replace("../", "")
val path = "${AppLoader.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
//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...
@@ -69,11 +69,12 @@ internal object WriteLayerDataZip {
}*/
val outFile = File(path)
if (outFile.exists()) outFile.delete()
outFile.createNewFile()
//val outFile = File(path)
//if (outFile.exists()) outFile.delete()
//outFile.createNewFile()
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 8192)
//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) }
@@ -196,15 +197,12 @@ internal object WriteLayerDataZip {
// END OF WRITE //
//////////////////
// replace savemeta with tempfile
try {
outputStream.flush()
outputStream.close()
return outFile
return outputStream.toByteArray64()
}
catch (e: IOException) {
e.printStackTrace()

View File

@@ -1,16 +1,15 @@
package net.torvald.terrarum.serialise
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
import net.torvald.terrarum.modulebasegame.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.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
object WriteWorldInfo {
@@ -23,12 +22,11 @@ object WriteWorldInfo {
/**
* 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
* @return List of ByteArray64, worldinfo0..worldinfo3; `null` on failure
*/
internal operator fun invoke(): List<File>? {
val world = (Terrarum.ingame!!.world)
internal operator fun invoke(world: GameWorld): List<ByteArray64>? {
val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
//val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
val infileList = arrayOf(
ModMgr.getGdxFilesFromEveryMod("blocks/blocks.csv"),
@@ -36,40 +34,34 @@ object WriteWorldInfo {
ModMgr.getGdxFilesFromEveryMod("materials/materials.csv")
)
val metaFile = File(path + "0")
val outFiles = ArrayList<File>()
outFiles.add(metaFile)
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 outFile = File(path + filenum.toString())
if (outFile.exists()) outFile.delete()
outFile.createNewFile()
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 256)
val outputStream = ByteArray64GrowableOutputStream()
val infile = infileList[filenum - 1]
infile.forEach {
outputStream.write("## from file: ${it.nameWithoutExtension()} ##############################\n".toByteArray())
val readBytes = it.readBytes()
outputStream.write(readBytes)
outputStream.write("\n".toByteArray())
}
outputStream.flush()
outputStream.close()
outFiles.add(outFile)
outFiles.add(outputStream.toByteArray64())
worldInfoHash.add(DigestUtils.sha256(FileInputStream(outFile)))
worldInfoHash.add(DigestUtils.sha256(ByteArray64InputStream(outputStream.toByteArray64())))
}
// compose save meta (actual writing part)
val metaOut = BufferedOutputStream(FileOutputStream(metaFile), 256)
val metaOut = ByteArray64GrowableOutputStream()
metaOut.write(META_MAGIC)
@@ -78,8 +70,11 @@ object WriteWorldInfo {
// world name
val worldNameBytes = world.worldName.toByteArray(Charsets.UTF_8)
metaOut.write(worldNameBytes)
if (worldNameBytes.last() != NULL) metaOut.write(NULL.toInt())
//metaOut.write(worldNameBytes)
worldNameBytes.forEach {
if (it != 0.toByte()) metaOut.write(it.toInt())
}
metaOut.write(NULL.toInt())
// terrain seed
metaOut.write(world.generatorSeed.toLittle())
@@ -125,7 +120,7 @@ object WriteWorldInfo {
return outFiles.toList()
return listOf(metaOut.toByteArray64()) + outFiles.toList()
}
}

View File

@@ -0,0 +1,69 @@
import com.badlogic.gdx.graphics.Color
import com.google.gson.GsonBuilder
import net.torvald.terrarum.gameactors.Actor
import org.dyn4j.geometry.Vector2
/**
* My anecdotes: GSON does NOT like anonymous class!
*
* Created by minjaesong on 2019-02-22
*/
object GsonTest {
operator fun invoke() {
val jsonString = GsonBuilder()
.setPrettyPrinting()
.serializeNulls()
.create()
.toJson(GsonTestActor())
println(jsonString)
}
}
class GsonTestActor : Actor(Actor.RenderOrder.MIDDLE) {
override fun update(delta: Float) {
TODO("not implemented")
}
override fun onActorValueChange(key: String, value: Any?) {
TODO("not implemented")
}
override fun dispose() {
TODO("not implemented")
}
override fun run() {
TODO("not implemented")
}
init {
referenceID = 42
}
private val mysecretnote = "note"
}
class GsonTestClass : GsonTestInterface {
var foo = 1.567f
val bar = "stingy"
val baz = Color(0f, 0.2f, 0.4f, 1f)
val fov = Vector2(1.324324, -0.4321)
val bazget: Color
get() = Color.CHARTREUSE
@Transient override var superfoo = 42
@Transient val tbar = "i'm invisible"
}
interface GsonTestInterface {
var superfoo: Int
}
fun main(args: Array<String>) {
GsonTest.invoke()
}

View File

@@ -228,9 +228,9 @@ class BasicDebugInfoWindow : UICanvas() {
*/
if (ingame != null) {
Terrarum.fontSmallNumbers.draw(batch, "${ccY}Actors total $ccG${ingame!!.actorContainer.size + ingame!!.actorContainerInactive.size}",
Terrarum.fontSmallNumbers.draw(batch, "${ccY}Actors total $ccG${ingame!!.actorContainerActive.size + ingame!!.actorContainerInactive.size}",
2f, Terrarum.HEIGHT - 10f)
Terrarum.fontSmallNumbers.draw(batch, "${ccY}Active $ccG${ingame!!.actorContainer.size}",
Terrarum.fontSmallNumbers.draw(batch, "${ccY}Active $ccG${ingame!!.actorContainerActive.size}",
(2 + 17 * 8).toFloat(), Terrarum.HEIGHT - 10f)
Terrarum.fontSmallNumbers.draw(batch, "${ccY}Dormant $ccG${ingame!!.actorContainerInactive.size}",
(2 + 28 * 8).toFloat(), Terrarum.HEIGHT - 10f)

View File

@@ -866,7 +866,7 @@ internal object BlocksDrawer {
fun dispose() {
printdbg(this, "dispose called by")
Thread.currentThread().stackTrace.forEach {
printdbg(this, it)
printdbg(this, "--> $it")
}
weatherTerrains.forEach { it.dispose() }

View File

@@ -241,7 +241,7 @@ object LightmapRendererOld {
private fun buildLanternmap() {
lanternMap.clear()
Terrarum.ingame?.let {
it.actorContainer.forEach { it ->
it.actorContainerActive.forEach { it ->
if (it is Luminous && it is ActorWBMovable) {
// put lanterns to the area the luminantBox is occupying
for (lightBox in it.lightBoxList) {

View File

@@ -5,10 +5,13 @@ Files contained the TerranVirtualDisk is as follows:
(root)
worldinfo0 -- Savegame Metadata (TESV)
Has fixed Entry ID of 32767
Has fixed Entry ID of 32766
worldinfo1 -- Copy of blocks.csv -- will use this from the next load
Has fixed Entry ID of 32765
worldinfo2 -- Copy of items.csv -- will use this from the next load
Has fixed Entry ID of 32764
worldinfo3 -- Copy of materials.csv -- will use this from the next load
Has fixed Entry ID of 32763
world[n] -- Layer Data (TEMD); [n] is a serial number of the world (starts at 1)
Has fixed Entry ID of [n]
(any random number in Hex 32768..ACTORID_MIN - 1) -- Serialised Dynamic Item?
@@ -16,6 +19,7 @@ Files contained the TerranVirtualDisk is as follows:
(PLAYER_REF_ID in Hex -- 91A7E2) -- Player Character Information (Serialised--JSON'd--Entity Information)
(51621D) -- The Debug Player (Serialised Entity Information)
load_order.txt -- LoadOrder.csv (NOT zipped)
Has fixed Entry ID of 32767

View File

@@ -14,25 +14,28 @@ Ord Hex Description
05 03 Number of hashes
06 Name of the world in UTF-8 (arbitrary length, must not contain NULL)
... 00 String terminator
n-1 00 String terminator
... Terrain seed (8 bytes)
... Randomiser s0 (8 bytes)
... Randomiser s1 (8 bytes)
... Weather s0 (8 bytes)
... Weather s1 (8 bytes)
(Ord is now offset from n)
... ReferenceID of the player (4 bytes, a fixed value of 91A7E2)
... Current world's time_t (the ingame time, 8 bytes)
00 Terrain seed (8 bytes)
08 Randomiser s0 (8 bytes)
10 Randomiser s1 (8 bytes)
18 Weather s0 (8 bytes)
20 Weather s1 (8 bytes)
... Creation time in time_t (6 bytes)
... Last play time in time_t (6 bytes)
... Total playtime in time_t (4 bytes) // will record 136.1 years of playtime
28 ReferenceID of the player (4 bytes, a fixed value of 91A7E2)
2C Current world's time_t (the ingame time, 8 bytes)
... SHA-256 hash of worldinfo1 (32 bytes)
... SHA-256 hash of worldinfo2 (32 bytes)
... SHA-256 hash of worldinfo3 (32 bytes)
34 Creation time in time_t (6 bytes)
3A Last play time in time_t (6 bytes)
40 Total playtime in time_t (4 bytes) // will record 136.1 years of playtime
... Gzipped thumbnail image in TGA format
44 SHA-256 hash of worldinfo1 (32 bytes)
72 SHA-256 hash of worldinfo2 (32 bytes)
A4 SHA-256 hash of worldinfo3 (32 bytes)
D6 Compressed size (2 bytes)
D8 Gzipped thumbnail image in TGA format
(it's gzipped so that it saves faster, so no Lzma)