diff --git a/lib/TerranVirtualDisk.jar b/lib/TerranVirtualDisk.jar new file mode 100644 index 000000000..9a9981c7b Binary files /dev/null and b/lib/TerranVirtualDisk.jar differ diff --git a/lib/gson-2.5.jar b/lib/gson-2.5.jar deleted file mode 100644 index 5c35c5d5c..000000000 Binary files a/lib/gson-2.5.jar and /dev/null differ diff --git a/lib/gson-2.8.5.jar b/lib/gson-2.8.5.jar new file mode 100644 index 000000000..0d5baf3fa Binary files /dev/null and b/lib/gson-2.8.5.jar differ diff --git a/lib/javadoc/gson-2.5-javadoc.jar b/lib/javadoc/gson-2.5-javadoc.jar deleted file mode 100644 index db7c5f5aa..000000000 Binary files a/lib/javadoc/gson-2.5-javadoc.jar and /dev/null differ diff --git a/lib/javadoc/gson-2.8.5-javadoc.jar b/lib/javadoc/gson-2.8.5-javadoc.jar new file mode 100644 index 000000000..e74a1d24c Binary files /dev/null and b/lib/javadoc/gson-2.8.5-javadoc.jar differ diff --git a/lib/source/TerranVirtualDisk-src.jar b/lib/source/TerranVirtualDisk-src.jar new file mode 100644 index 000000000..a88b2c608 Binary files /dev/null and b/lib/source/TerranVirtualDisk-src.jar differ diff --git a/lib/source/gson-2.8.5-sources.jar b/lib/source/gson-2.8.5-sources.jar new file mode 100644 index 000000000..a64f4e732 Binary files /dev/null and b/lib/source/gson-2.8.5-sources.jar differ diff --git a/src/net/torvald/spriteanimation/SpriteAnimation.kt b/src/net/torvald/spriteanimation/SpriteAnimation.kt index ed1534916..0eaace654 100644 --- a/src/net/torvald/spriteanimation/SpriteAnimation.kt +++ b/src/net/torvald/spriteanimation/SpriteAnimation.kt @@ -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 diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt index 34c56f6a0..ff05972b9 100644 --- a/src/net/torvald/terrarum/IngameInstance.kt +++ b/src/net/torvald/terrarum/IngameInstance.kt @@ -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(ACTORCONTAINER_INITIAL_SIZE) + val actorContainerActive = ArrayList(ACTORCONTAINER_INITIAL_SIZE) val actorContainerInactive = ArrayList(ACTORCONTAINER_INITIAL_SIZE) protected val terrainChangeQueue = Queue() @@ -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!!) diff --git a/src/net/torvald/terrarum/KVHashMap.kt b/src/net/torvald/terrarum/KVHashMap.kt index a1cb36307..d137c68ee 100644 --- a/src/net/torvald/terrarum/KVHashMap.kt +++ b/src/net/torvald/terrarum/KVHashMap.kt @@ -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) { diff --git a/src/net/torvald/terrarum/gameactors/Actor.kt b/src/net/torvald/terrarum/gameactors/Actor.kt index 7a158c51e..42fd2ff2f 100644 --- a/src/net/torvald/terrarum/gameactors/Actor.kt +++ b/src/net/torvald/terrarum/gameactors/Actor.kt @@ -35,7 +35,7 @@ abstract class Actor(val renderOrder: RenderOrder) : Comparable, 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 { diff --git a/src/net/torvald/terrarum/gameactors/ActorValue.kt b/src/net/torvald/terrarum/gameactors/ActorValue.kt index d3dd55da8..71ad2da48 100644 --- a/src/net/torvald/terrarum/gameactors/ActorValue.kt +++ b/src/net/torvald/terrarum/gameactors/ActorValue.kt @@ -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): this(actor) { hashMap = newMap diff --git a/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt b/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt index 38940a511..862d1e0f1 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt @@ -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) diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index f4640e339..c78e217e2 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -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) } } diff --git a/src/net/torvald/terrarum/itemproperties/GameItem.kt b/src/net/torvald/terrarum/itemproperties/GameItem.kt index 9c3a2ae01..69b5bc7da 100644 --- a/src/net/torvald/terrarum/itemproperties/GameItem.kt +++ b/src/net/torvald/terrarum/itemproperties/GameItem.kt @@ -74,9 +74,11 @@ abstract class GameItem : Comparable, 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, Cloneable { fun generateUniqueDynamicID(inventory: ActorInventory): GameItem { dynamicID = GameItem.generateUniqueDynamicID(inventory) + ItemCodex.registerNewDynamicItem(dynamicID, this) return this } @@ -285,6 +288,7 @@ abstract class GameItem : Comparable, Cloneable { do { ret = ITEM_DYNAMIC.pickRandom() } while (inventory.contains(ret)) + return ret } } diff --git a/src/net/torvald/terrarum/itemproperties/ItemCodex.kt b/src/net/torvald/terrarum/itemproperties/ItemCodex.kt index c95d31e1a..bf729f272 100644 --- a/src/net/torvald/terrarum/itemproperties/ItemCodex.kt +++ b/src/net/torvald/terrarum/itemproperties/ItemCodex.kt @@ -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() - private val dynamicItemDescription = HashMap() + val dynamicItemDescription = HashMap() 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 diff --git a/src/net/torvald/terrarum/modulebasegame/EntryPoint.kt b/src/net/torvald/terrarum/modulebasegame/EntryPoint.kt index 9cf0e2d35..1379703bf 100644 --- a/src/net/torvald/terrarum/modulebasegame/EntryPoint.kt +++ b/src/net/torvald/terrarum/modulebasegame/EntryPoint.kt @@ -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 } diff --git a/src/net/torvald/terrarum/modulebasegame/Ingame.kt b/src/net/torvald/terrarum/modulebasegame/Ingame.kt index de9c3050a..a644c4b4d 100644 --- a/src/net/torvald/terrarum/modulebasegame/Ingame.kt +++ b/src/net/torvald/terrarum/modulebasegame/Ingame.kt @@ -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(ACTORCONTAINER_INITIAL_SIZE) + //val actorContainerActive = ArrayList(ACTORCONTAINER_INITIAL_SIZE) //val actorContainerInactive = ArrayList(ACTORCONTAINER_INITIAL_SIZE) val particlesContainer = CircularArray(PARTICLES_MAX) val uiContainer = ArrayList() @@ -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") } } diff --git a/src/net/torvald/terrarum/modulebasegame/console/ActorsList.kt b/src/net/torvald/terrarum/modulebasegame/console/ActorsList.kt index 30cded045..afb68ec97 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ActorsList.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ActorsList.kt @@ -15,7 +15,7 @@ internal object ActorsList : ConsoleCommand { override fun execute(args: Array) { jPanelInstances.add(ActorsLister( - (Terrarum.ingame!! as Ingame).actorContainer, + (Terrarum.ingame!! as Ingame).actorContainerActive, (Terrarum.ingame!! as Ingame).actorContainerInactive) ) } diff --git a/src/net/torvald/terrarum/modulebasegame/console/ExportLayerData.kt b/src/net/torvald/terrarum/modulebasegame/console/ExportLayerData.kt index ac9418b63..b1535d0f0 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ExportLayerData.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ExportLayerData.kt @@ -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) { - 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() { diff --git a/src/net/torvald/terrarum/modulebasegame/console/GsonTest.kt b/src/net/torvald/terrarum/modulebasegame/console/GsonTest.kt index d2ac6ca5b..408b0c371 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/GsonTest.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/GsonTest.kt @@ -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) { 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 diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorHumanoid.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorHumanoid.kt index c6aad2ec1..0bf86e9d8 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorHumanoid.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorHumanoid.kt @@ -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 diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorInventory.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorInventory.kt index b08f01234..0cefaa677 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorInventory.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorInventory.kt @@ -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.binarySearch(ID: ItemID, searchBy: Int): Int { // code from collections/Collections.kt var low = 0 diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ParticleBase.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ParticleBase.kt index 16d3eac7c..b2700dbe4 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ParticleBase.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ParticleBase.kt @@ -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) } diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/PlayableActorDelegate.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/PlayableActorDelegate.kt index 2580d6f79..aab3763db 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/PlayableActorDelegate.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/PlayableActorDelegate.kt @@ -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) diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ThreadActorUpdate.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ThreadActorUpdate.kt index 0e71d8a78..8a21419d4 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ThreadActorUpdate.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ThreadActorUpdate.kt @@ -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) { diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/physicssolver/CollisionSolver.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/physicssolver/CollisionSolver.kt index 8b27cea08..d50ae133e 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/physicssolver/CollisionSolver.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/physicssolver/CollisionSolver.kt @@ -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)) diff --git a/src/net/torvald/terrarum/modulebasegame/items/PickaxeGeneric.kt b/src/net/torvald/terrarum/modulebasegame/items/PickaxeGeneric.kt index ea2c86c06..53fbc140f 100644 --- a/src/net/torvald/terrarum/modulebasegame/items/PickaxeGeneric.kt +++ b/src/net/torvald/terrarum/modulebasegame/items/PickaxeGeneric.kt @@ -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 } diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/luaapi/FilesystemTEVD.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/luaapi/FilesystemTEVD.kt index b36a7fc90..089f578d6 100644 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/luaapi/FilesystemTEVD.kt +++ b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/luaapi/FilesystemTEVD.kt @@ -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 } - } + }*/ } diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/ByteArray64.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/ByteArray64.kt deleted file mode 100644 index fef99bde3..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/ByteArray64.kt +++ /dev/null @@ -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 - - init { - if (size < 0) - throw IllegalArgumentException("Invalid array size!") - - val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt() - - __data = Array( - 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 - } -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/DiskSkimmer.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/DiskSkimmer.kt deleted file mode 100644 index 7ec0f9898..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/DiskSkimmer.kt +++ /dev/null @@ -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() - - - 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() - // 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() - } -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/Spec.md b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/Spec.md deleted file mode 100644 index 7f1bc5ec9..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/Spec.md +++ /dev/null @@ -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 - - - IndexNumber - - - IndexNumber - - - ... - - 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 - 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) - 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) - Actual Contents, DEFLATEd payload - - (Header size: 12 bytes) - -### Entry of Directory - Uint16 Number of entries (normal files, other directories, symlinks) - 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 - - Uint8[2] 0xFF 0x19 (EOF mark) diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/VDUtil.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/VDUtil.kt deleted file mode 100644 index 2c7f3b4dd..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/VDUtil.kt +++ /dev/null @@ -1,1093 +0,0 @@ -package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd - -import java.io.* -import java.nio.charset.Charset -import java.util.* -import java.util.logging.Level -import java.util.zip.DeflaterOutputStream -import java.util.zip.GZIPInputStream -import java.util.zip.GZIPOutputStream -import java.util.zip.InflaterOutputStream -import javax.naming.OperationNotSupportedException -import kotlin.collections.ArrayList - -/** - * Created by minjaesong on 2017-04-01. - */ -object VDUtil { - class VDPath() { - /** - * input: (root)->etc->boot in Constructor - * output: ByteArrayListOf( - * e t c \0 \0 \0 \0 \0 ... , - * b o o t \0 \0 \0 \0 ... - * ) - * - * input: "/" - * interpretation: (root) - * output: ByteArrayListOf( - * ) - */ - var hierarchy = ArrayList() - - val lastIndex: Int - get() = hierarchy.lastIndex - fun last(): ByteArray = hierarchy.last() - - constructor(strPath: String, charset: Charset) : this() { - val unsanitisedHierarchy = ArrayList() - strPath.sanitisePath().split('/').forEach { unsanitisedHierarchy.add(it) } - - // deal with bad slashes (will drop '' and tail '') - // "/bin/boot/drivers/" -> "bin/boot/drivers" - // removes head slash - if (unsanitisedHierarchy[0].isEmpty()) - unsanitisedHierarchy.removeAt(0) - // removes tail slash - if (unsanitisedHierarchy.size > 0 && - unsanitisedHierarchy[unsanitisedHierarchy.lastIndex].isEmpty()) - unsanitisedHierarchy.removeAt(unsanitisedHierarchy.lastIndex) - - unsanitisedHierarchy.forEach { - hierarchy.add(it.toEntryName(DiskEntry.NAME_LENGTH, charset)) - } - } - - private constructor(newHierarchy: ArrayList) : this() { - hierarchy = newHierarchy - } - - override fun toString(): String { - val sb = StringBuilder() - if (hierarchy.size > 0) { - sb.append(hierarchy[0].toCanonicalString(Charsets.UTF_8)) - } - if (hierarchy.size > 1) { - (1..hierarchy.lastIndex).forEach { - sb.append('/') - sb.append(hierarchy[it].toCanonicalString(Charsets.UTF_8)) - } - } - - return sb.toString() - } - - operator fun get(i: Int) = hierarchy[i] - fun forEach(action: (ByteArray) -> Unit) = hierarchy.forEach(action) - fun forEachIndexed(action: (Int, ByteArray) -> Unit) = hierarchy.forEachIndexed(action) - - fun getParent(ancestorCount: Int = 1): VDPath { - val newPath = ArrayList() - hierarchy.forEach { newPath.add(it) } - - repeat(ancestorCount) { newPath.removeAt(newPath.lastIndex) } - return VDPath(newPath) - } - } - - fun File.writeBytes64(array: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) { - array.writeToFile(this) - } - - fun File.readBytes64(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 { - val inbytes = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(this.length()) - val inputStream = BufferedInputStream(FileInputStream(this)) - var readInt = inputStream.read() - var readInCounter = 0L - while (readInt != -1) { - inbytes[readInCounter] = readInt.toByte() - readInCounter += 1 - - readInt = inputStream.read() - } - inputStream.close() - - return inbytes - } - - fun dumpToRealMachine(disk: VirtualDisk, outfile: File) { - if (!outfile.exists()) outfile.createNewFile() - outfile.writeBytes64(disk.serialize().array) - } - - /** - * Reads serialised binary and returns corresponding VirtualDisk instance. - * - * @param crcWarnLevel Level.OFF -- no warning, Level.WARNING -- print out warning, Level.SEVERE -- throw error - */ - fun readDiskArchive(infile: File, crcWarnLevel: Level = Level.SEVERE, warningFunc: ((String) -> Unit)? = null, charset: Charset): VirtualDisk { - val inbytes = infile.readBytes64() - - - - if (magicMismatch(VirtualDisk.MAGIC, inbytes.sliceArray64(0L..3L).toByteArray())) - throw RuntimeException("Invalid Virtual Disk file!") - - val diskSize = inbytes.sliceArray64(4L..9L).toInt48Big() - val diskName = inbytes.sliceArray64(10L..10L + 31) - val diskCRC = inbytes.sliceArray64(10L + 32..10L + 32 + 3).toIntBig() // to check with completed vdisk - val diskSpecVersion = inbytes[10L + 32 + 4] - - - if (diskSpecVersion != specversion) - throw RuntimeException("Unsupported disk format version: current internal version is $specversion; the file's version is $diskSpecVersion") - - val vdisk = VirtualDisk(diskSize, diskName.toByteArray()) - - //println("[VDUtil] currentUnixtime = $currentUnixtime") - - var entryOffset = VirtualDisk.HEADER_SIZE - // not footer, entries - while (!Arrays.equals(inbytes.sliceArray64(entryOffset..entryOffset + 3).toByteArray(), VirtualDisk.FOOTER_START_MARK)) { - //println("[VDUtil] entryOffset = $entryOffset") - // read and prepare all the shits - val entryID = inbytes.sliceArray64(entryOffset..entryOffset + 3).toIntBig() - val entryParentID = inbytes.sliceArray64(entryOffset + 4..entryOffset + 7).toIntBig() - val entryTypeFlag = inbytes[entryOffset + 8] - val entryFileName = inbytes.sliceArray64(entryOffset + 9..entryOffset + 9 + 255).toByteArray() - val entryCreationTime = inbytes.sliceArray64(entryOffset + 265..entryOffset + 270).toInt48Big() - val entryModifyTime = inbytes.sliceArray64(entryOffset + 271..entryOffset + 276).toInt48Big() - val entryCRC = inbytes.sliceArray64(entryOffset + 277..entryOffset + 280).toIntBig() // to check with completed entry - - val entryData = when (entryTypeFlag) { - DiskEntry.NORMAL_FILE -> { - val filesize = inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 5).toInt48Big() - //println("[VDUtil] --> is file; filesize = $filesize") - inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE + 6..entryOffset + DiskEntry.HEADER_SIZE + 5 + filesize) - } - DiskEntry.DIRECTORY -> { - val entryCount = inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 1).toShortBig() - //println("[VDUtil] --> is directory; entryCount = $entryCount") - inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE + 2..entryOffset + DiskEntry.HEADER_SIZE + 1 + entryCount * 4) - } - DiskEntry.SYMLINK -> { - inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 3) - } - else -> throw RuntimeException("Unknown entry with type $entryTypeFlag at entryOffset $entryOffset") - } - - - - // update entryOffset so that we can fetch next entry in the binary - entryOffset += DiskEntry.HEADER_SIZE + entryData.size + when (entryTypeFlag) { - DiskEntry.COMPRESSED_FILE -> 12 // PLEASE DO REFER TO Spec.md - DiskEntry.NORMAL_FILE -> 6 // PLEASE DO REFER TO Spec.md - DiskEntry.DIRECTORY -> 2 // PLEASE DO REFER TO Spec.md - DiskEntry.SYMLINK -> 0 // PLEASE DO REFER TO Spec.md - else -> throw RuntimeException("Unknown entry with type $entryTypeFlag") - } - - - // create entry - val diskEntry = DiskEntry( - entryID = entryID, - parentEntryID = entryParentID, - filename = entryFileName, - creationDate = entryCreationTime, - modificationDate = entryModifyTime, - contents = if (entryTypeFlag == DiskEntry.NORMAL_FILE) { - EntryFile(entryData) - } - else if (entryTypeFlag == DiskEntry.DIRECTORY) { - val entryList = ArrayList() - (0..entryData.size / 4 - 1).forEach { - entryList.add(entryData.sliceArray64(4 * it..4 * it + 3).toIntBig()) - } - - EntryDirectory(entryList) - } - else if (entryTypeFlag == DiskEntry.SYMLINK) { - EntrySymlink(entryData.toIntBig()) - } - else - throw RuntimeException("Unknown entry with type $entryTypeFlag") - ) - - // check CRC of entry - if (crcWarnLevel == Level.SEVERE || crcWarnLevel == Level.WARNING) { - val calculatedCRC = diskEntry.hashCode() - - val crcMsg = "CRC failed: expected ${entryCRC.toHex()}, got ${calculatedCRC.toHex()}\n" + - "at file \"${diskEntry.getFilenameString(charset)}\" (entry ID ${diskEntry.entryID})" - - if (calculatedCRC != entryCRC) { - if (crcWarnLevel == Level.SEVERE) - throw IOException(crcMsg) - else if (warningFunc != null) - warningFunc(crcMsg) - } - } - - // add entry to disk - vdisk.entries[entryID] = diskEntry - } - // entries ends, footers are to be read - run { - entryOffset += 4 // skip footer marker - - val footerSize = inbytes.size - entryOffset - VirtualDisk.EOF_MARK.size - if (footerSize > 0) { - vdisk.__internalSetFooter__(inbytes.sliceArray64(entryOffset..entryOffset + footerSize - 1)) - } - } - - - // check CRC of disk - if (crcWarnLevel == Level.SEVERE || crcWarnLevel == Level.WARNING) { - val calculatedCRC = vdisk.hashCode() - - val crcMsg = "Disk CRC failed: expected ${diskCRC.toHex()}, got ${calculatedCRC.toHex()}" - - if (calculatedCRC != diskCRC) { - if (crcWarnLevel == Level.SEVERE) - throw IOException(crcMsg) - else if (warningFunc != null) - warningFunc(crcMsg) - } - } - - return vdisk - } - - - fun isFile(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntryFile - fun isCompressedFile(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntryFileCompressed - fun isDirectory(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntryDirectory - fun isSymlink(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntrySymlink - - /** - * Get list of entries of directory. - */ - fun getDirectoryEntries(disk: VirtualDisk, dirToSearch: DiskEntry): Array { - if (dirToSearch.contents !is EntryDirectory) - throw IllegalArgumentException("The entry is not directory") - - val entriesList = ArrayList() - dirToSearch.contents.forEach { - val entry = disk.entries[it] - if (entry != null) entriesList.add(entry) - } - - return entriesList.toTypedArray() - } - /** - * Get list of entries of directory. - */ - fun getDirectoryEntries(disk: VirtualDisk, entryID: EntryID): Array { - val entry = disk.entries[entryID] - if (entry == null) { - throw IOException("Entry does not exist") - } - else { - return getDirectoryEntries(disk, entry) - } - } - - /** - * Search a entry using path - * @return Pair of , or null if not found - */ - fun getFile(disk: VirtualDisk, path: VDPath): DiskEntry? { - val searchHierarchy = ArrayList() - fun getCurrentEntry(): DiskEntry = searchHierarchy.last() - //var currentDirectory = disk.root - - searchHierarchy.add(disk.entries[0]!!) - - // path of root - if (path.hierarchy.size == 0) { - return disk.entries[0]!! - } - - try { - // search for the file - path.forEachIndexed { i, nameToSearch -> - // if we hit the last elem, we won't search more - if (i <= path.lastIndex) { - val currentDirEntries = getDirectoryEntries(disk, getCurrentEntry()) - - var fileFound: DiskEntry? = null - for (entry in currentDirEntries) { - if (Arrays.equals(entry.filename, nameToSearch)) { - fileFound = entry - break - } - } - if (fileFound == null) { // file not found - throw KotlinNullPointerException() - } - else { // file found - searchHierarchy.add(fileFound) - } - } - } - } - catch (e1: KotlinNullPointerException) { - return null - } - - // file found - return searchHierarchy[searchHierarchy.lastIndex] - } - - /** - * SYNOPSIS disk.getFile("bin/msh.lua")!!.file.getAsNormalFile(disk) - * - * Use VirtualDisk.getAsNormalFile(path) - */ - private fun DiskEntry.getAsNormalFile(disk: VirtualDisk): EntryFile = - this.contents as? EntryFile ?: - if (this.contents is EntryDirectory) - throw RuntimeException("this is directory") - else if (this.contents is EntrySymlink) - disk.entries[this.contents.target]!!.getAsNormalFile(disk) - else - throw RuntimeException("Unknown entry type") - /** - * SYNOPSIS disk.getFile("bin/msh.lua")!!.first.getAsNormalFile(disk) - * - * Use VirtualDisk.getAsNormalFile(path) - */ - private fun DiskEntry.getAsDirectory(disk: VirtualDisk): EntryDirectory = - this.contents as? EntryDirectory ?: - if (this.contents is EntrySymlink) - disk.entries[this.contents.target]!!.getAsDirectory(disk) - else if (this.contents is EntryFile) - throw RuntimeException("this is not directory") - else - throw RuntimeException("Unknown entry type") - - /** - * Search for the file and returns a instance of normal file. - */ - fun getAsNormalFile(disk: VirtualDisk, path: VDPath) = - getFile(disk, path)!!.getAsNormalFile(disk) - /** - * Fetch the file and returns a instance of normal file. - */ - fun getAsNormalFile(disk: VirtualDisk, entryIndex: EntryID) = - disk.entries[entryIndex]!!.getAsNormalFile(disk) - /** - * Search for the file and returns a instance of directory. - */ - fun getAsDirectory(disk: VirtualDisk, path: VDPath) = - getFile(disk, path)!!.getAsDirectory(disk) - /** - * Fetch the file and returns a instance of directory. - */ - fun getAsDirectory(disk: VirtualDisk, entryIndex: EntryID) = - disk.entries[entryIndex]!!.getAsDirectory(disk) - /** - * Deletes file on the disk safely. - */ - fun deleteFile(disk: VirtualDisk, path: VDPath) { - val fileSearchResult = getFile(disk, path)!! - return deleteFile(disk, fileSearchResult.entryID) - } - /** - * Deletes file on the disk safely. - */ - fun deleteFile(disk: VirtualDisk, targetID: EntryID) { - disk.checkReadOnly() - - val file = disk.entries[targetID] - - if (file == null) { - throw FileNotFoundException("No such file to delete") - } - - val parentID = file.parentEntryID - val parentDir = getAsDirectory(disk, parentID) - - fun rollback() { - if (!disk.entries.contains(targetID)) { - disk.entries[targetID] = file - } - if (!parentDir.contains(targetID)) { - parentDir.add(targetID) - } - } - - // check if directory "parentID" has "targetID" in the first place - if (!directoryContains(disk, parentID, targetID)) { - throw FileNotFoundException("No such file to delete") - } - else if (targetID == 0) { - throw IOException("Cannot delete root file system") - } - else if (file.contents is EntryDirectory && file.contents.entryCount > 0) { - deleteDirRecurse(disk, targetID) - } - else { - try { - // delete file record - disk.entries.remove(targetID) - // unlist file from parent directly - parentDir.remove(targetID) - } - catch (e: Exception) { - rollback() - throw InternalError("Unknown error *sigh* It's annoying, I know.") - } - } - } - /** - * Changes the name of the entry. - */ - fun renameFile(disk: VirtualDisk, path: VDPath, newName: String, charset: Charset) { - val file = getFile(disk, path) - - if (file != null) { - file.filename = newName.sanitisePath().toEntryName(DiskEntry.NAME_LENGTH, charset) - } - else { - throw FileNotFoundException() - } - } - /** - * Changes the name of the entry. - */ - fun renameFile(disk: VirtualDisk, fileID: EntryID, newName: String, charset: Charset) { - val file = disk.entries[fileID] - - if (file != null) { - file.filename = newName.sanitisePath().toEntryName(DiskEntry.NAME_LENGTH, charset) - } - else { - throw FileNotFoundException() - } - } - /** - * Add file to the specified directory. - * The file will get new EntryID and its ParentID will be overwritten. - */ - fun addFile(disk: VirtualDisk, parentPath: VDPath, file: DiskEntry, compress: Boolean = false) { - val targetDirID = getFile(disk, parentPath)!!.entryID - return addFile(disk, targetDirID, file, compress) - } - - fun randomBase62(length: Int): String { - val glyphs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" - val sb = StringBuilder() - - kotlin.repeat(length) { - sb.append(glyphs[(Math.random() * glyphs.length).toInt()]) - } - - return sb.toString() - } - - /** - * Add file to the specified directory. ParentID of the file will be overwritten. - * - * @param compressTheFile Used to compress un-compressed file. Will be ignored if the entry is Dir, Symlink or EntryFileCompressed - */ - fun addFile(disk: VirtualDisk, directoryID: EntryID, file: DiskEntry, compressTheFile: Boolean = false) { - disk.checkReadOnly() - disk.checkCapacity(file.serialisedSize) - - try { - // generate new ID for the file - file.entryID = disk.generateUniqueID() - // add record to the directory - getAsDirectory(disk, directoryID).add(file.entryID) - - // DEFLATE fat boy if marked as - if (compressTheFile && file.contents is EntryFile) { - val filename = "./tmp_" + randomBase62(10) - - // dump the deflated bytes to disk - file.contents.bytes.forEachBanks { - val fos = BufferedOutputStream(FileOutputStream(filename)) - val deflater = DeflaterOutputStream(fos, true) - - deflater.write(it) - deflater.flush() - deflater.close() - } - - - // read back deflated bytes untouched and store it - val tempFile = File(filename) - val tempFileFIS = FileInputStream(tempFile) - val readBytes = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(tempFile.length()) - - var c = 0L - - while (true) { - val r = tempFileFIS.read() - if (r == -1) break - else readBytes[c] = r.toByte() - - c++ - } - - tempFileFIS.close() - - - val newContent = EntryFileCompressed(file.contents.bytes.size, readBytes) - val newEntry = DiskEntry( - file.entryID, file.parentEntryID, file.filename, file.creationDate, file.modificationDate, - newContent - ) - - disk.entries[file.entryID] = newEntry - } - // just the add the boy to the house - else { - disk.entries[file.entryID] = file - } - - // make this boy recognise his new parent - file.parentEntryID = directoryID - } - catch (e: KotlinNullPointerException) { - throw FileNotFoundException("No such directory") - } - } - /** - * Add subdirectory to the specified directory. - * - * @return EntryID of newly created directory - */ - fun addDir(disk: VirtualDisk, parentPath: VDPath, name: ByteArray): EntryID { - val parentID = getFile(disk, parentPath)!!.entryID - return addDir(disk, parentID, name) - } - /** - * Add file to the specified directory. - * - * @return EntryID of newly created directory - */ - fun addDir(disk: VirtualDisk, parentDir: EntryID, name: ByteArray): EntryID { - disk.checkReadOnly() - disk.checkCapacity(EntryDirectory.NEW_ENTRY_SIZE) - - val newID = disk.generateUniqueID() - - try { - // add record to the directory - getAsDirectory(disk, parentDir).add(newID) - // add entry on the disk - disk.entries[newID] = DiskEntry( - newID, - parentDir, - name, - currentUnixtime, - currentUnixtime, - EntryDirectory() - ) - - return newID - } - catch (e: KotlinNullPointerException) { - throw FileNotFoundException("No such directory") - } - } - - fun deleteDirRecurse(disk: VirtualDisk, directoryID: EntryID) { - val entriesToDelete = ArrayList() - - fun recurse1(entry: DiskEntry?) { - // return conditions - if (entry == null) return - if (entry.contents !is EntryDirectory) { - entriesToDelete.add(entry.entryID) - return - } - // recurse - else { - entry.contents.forEach { - entriesToDelete.add(entry.entryID) - recurse1(disk.entries[it]) - } - } - } - - - - val entry = disk.entries[directoryID] - if (entry != null && entry.contents is EntryDirectory) { - entry.contents.forEach { - entriesToDelete.add(directoryID) - recurse1(disk.entries[it]) - } - - // delete entries - entriesToDelete.forEach { disk.entries.remove(it) } - // GC - gcDumpAll(disk) - System.gc() - } - else if (entry == null) { - throw FileNotFoundException("No such directory") - } - else { - throw IOException("The file is not a directory") - } - } - - /** - * Imports external file and returns corresponding DiskEntry. - */ - fun importFile(file: File, newID: EntryID, charset: Charset): DiskEntry { - if (file.isDirectory) { - throw IOException("The file is a directory") - } - - return DiskEntry( - entryID = newID, - parentEntryID = 0, // placeholder - filename = file.name.toEntryName(DiskEntry.NAME_LENGTH, charset), - creationDate = currentUnixtime, - modificationDate = currentUnixtime, - contents = EntryFile(file.readBytes64()) - ) - } - - fun importDirRecurse(disk: VirtualDisk, dir: File, path: VDPath, charset: Charset) = - importDirRecurse(disk, dir, getFile(disk, path)!!.entryID, charset) - - fun importDirRecurse(disk: VirtualDisk, dir: File, superNode: EntryID, charset: Charset, newName: String? = null) { - fun recurse1(file: File, node: EntryID) { - // return conditions - if (!file.isDirectory) { - // if not a directory, add to node - val importedFile = importFile(file, disk.generateUniqueID(), charset) - addFile(disk, node, importedFile) - return - } - // recurse - else { - // mkdir - val newDir = addDir(disk, node, file.name.toEntryName(DiskEntry.NAME_LENGTH, charset)) - // for entries in this fileDirectory... - file.listFiles().forEach { recurse1(it, newDir) } - } - } - - - // mkdir to superNode - val newDir = addDir(disk, superNode, (newName ?: dir.name).toEntryName(DiskEntry.NAME_LENGTH, charset)) - // for entries in this fileDirectory... - dir.listFiles().forEach { recurse1(it, newDir) } - } - - /** - * Export file on the virtual disk into real disk. - */ - fun exportFile(entryFile: EntryFile, outfile: File) { - outfile.createNewFile() - - if (entryFile is EntryFileCompressed) { - entryFile.bytes.forEachBanks { - val fos = FileOutputStream(outfile) - val inflater = InflaterOutputStream(fos) - - inflater.write(it) - inflater.flush() - inflater.close() - } - } - else { - outfile.writeBytes64(entryFile.bytes) - } - } - - fun exportDirRecurse(disk: VirtualDisk, parentDir: EntryID, outfile: File, charset: Charset) { - fun recurse1(file: DiskEntry, dir: File) { - // return conditions - if (file.contents is EntryFile) { - // if not a directory, write as file - val newFile = File(dir, file.getFilenameString(charset)) - newFile.writeBytes64(file.contents.bytes) - return - } - // recurse - else if (file.contents is EntryDirectory) { - // mkdir - val newDir = File(dir, file.getFilenameString(charset)) - newDir.mkdir() - // for entries in this fileDirectory... - file.contents.forEach { recurse1(disk.entries[it]!!, newDir) } - } - } - - - // mkdir to superNode - val newDir = File(outfile, disk.entries[parentDir]!!.getFilenameString(charset)) - newDir.mkdir() - // for entries in this fileDirectory... - getDirectoryEntries(disk, parentDir).forEach { recurse1(it, newDir) } - } - - /** - * Check for name collision in specified directory. - */ - fun nameExists(disk: VirtualDisk, name: String, directoryID: EntryID, charset: Charset): Boolean { - return nameExists(disk, name.toEntryName(256, charset), directoryID) - } - /** - * Check for name collision in specified directory. - */ - fun nameExists(disk: VirtualDisk, name: ByteArray, directoryID: EntryID): Boolean { - val directoryContents = getDirectoryEntries(disk, directoryID) - directoryContents.forEach { - if (Arrays.equals(name, it.filename)) - return true - } - return false - } - - /** - * Move file from to there, overwrite - */ - fun moveFile(disk1: VirtualDisk, fromPath: VDPath, disk2: VirtualDisk, toPath: VDPath) { - val file = getFile(disk1, fromPath) - - // checking readOnly is redundant here - - if (file != null) { - disk2.checkCapacity(file.contents.getSizeEntry()) - - try { - deleteFile(disk2, toPath) - } - catch (e: KotlinNullPointerException) { /* Nothing to delete beforehand */ } - - deleteFile(disk1, fromPath) // any uncaught no_from_file will be caught here - try { - addFile(disk2, toPath.getParent(), file) - } - catch (e: FileNotFoundException) { - // roll back delete on disk1 - addFile(disk1, file.parentEntryID, file) - throw FileNotFoundException("No such destination") - } - } - else { - throw FileNotFoundException("No such file to move") - } - } - - - /** - * Creates new disk with given name and capacity - */ - fun createNewDisk(diskSize: Long, diskName: String, charset: Charset): VirtualDisk { - val newdisk = VirtualDisk(diskSize, diskName.toEntryName(VirtualDisk.NAME_LENGTH, charset)) - val rootDir = DiskEntry( - entryID = 0, - parentEntryID = 0, - filename = DiskEntry.ROOTNAME.toByteArray(charset), - creationDate = currentUnixtime, - modificationDate = currentUnixtime, - contents = EntryDirectory() - ) - - newdisk.entries[0] = rootDir - - return newdisk - } - /** - * Creates new zero-filled file with given name and size - */ - fun createNewBlankFile(disk: VirtualDisk, directoryID: EntryID, fileSize: Long, filename: String, charset: Charset): EntryID { - disk.checkReadOnly() - disk.checkCapacity(fileSize + DiskEntry.HEADER_SIZE + 4) - - val newEntry = DiskEntry( - disk.generateUniqueID(), - directoryID, - filename.toEntryName(DiskEntry.NAME_LENGTH, charset = charset), - currentUnixtime, - currentUnixtime, - EntryFile(fileSize) - ) - - addFile(disk, directoryID, newEntry) - - return newEntry.entryID - } - - - /** - * Throws an exception if the disk is read-only - */ - fun VirtualDisk.checkReadOnly() { - if (this.isReadOnly) - throw IOException("Disk is read-only") - } - /** - * Throws an exception if specified size cannot fit into the disk - */ - fun VirtualDisk.checkCapacity(newSize: Long) { - if (this.usedBytes + newSize > this.capacity) - throw IOException("Not enough space on the disk") - } - fun net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64.toIntBig(): Int { - if (this.size != 4L) - throw OperationNotSupportedException("ByteArray is not Int") - - var i = 0 - var c = 0 - this.forEach { byte -> i += byte.toUint().shl(24 - c * 8); c += 1 } - return i - } - fun net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64.toInt48Big(): Long { - if (this.size != 6L) - throw OperationNotSupportedException("ByteArray is not Long") - - var i = 0L - var c = 0 - this.forEach { byte -> i += byte.toUint().shl(40 - c * 8); c += 1 } - return i - } - fun net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64.toShortBig(): Short { - if (this.size != 2L) - throw OperationNotSupportedException("ByteArray is not Short") - - return (this[0].toUint().shl(256) + this[1].toUint()).toShort() - } - fun String.sanitisePath(): String { - val invalidChars = Regex("""[<>:"|?*\u0000-\u001F]""") - if (this.contains(invalidChars)) - throw IOException("path contains invalid characters") - - val path1 = this.replace('\\', '/') - return path1 - } - - fun resolveIfSymlink(disk: VirtualDisk, indexNumber: EntryID, recurse: Boolean = false): DiskEntry { - var entry: DiskEntry? = disk.entries[indexNumber] - if (entry == null) throw IOException("File does not exist") - if (entry.contents !is EntrySymlink) return entry - if (recurse) { - while (entry!!.contents is EntrySymlink) { - entry = disk.entries[(entry.contents as EntrySymlink).target] - if (entry == null) break - } - } - else { - entry = disk.entries[(entry.contents as EntrySymlink).target] - } - if (entry == null) throw IOException("Pointing file does not exist") - return entry - } - - val currentUnixtime: Long - get() = System.currentTimeMillis() / 1000 - - fun directoryContains(disk: VirtualDisk, dirID: EntryID, targetID: EntryID): Boolean { - val dir = resolveIfSymlink(disk, dirID) - - if (dir.contents !is EntryDirectory) { - throw FileNotFoundException("Not a directory") - } - else { - return dir.contents.contains(targetID) - } - } - - /** - * Searches for disconnected nodes using its parent pointer. - * If the parent node is invalid, the node is considered orphan, and will be added - * to the list this function returns. - * - * @return List of orphan entries - */ - fun gcSearchOrphan(disk: VirtualDisk): List { - return disk.entries.filter { disk.entries[it.value.parentEntryID] == null }.keys.toList() - } - - /** - * Searches for null-pointing entries (phantoms) within every directory. - * - * @return List of search results, which is Pair(directory that contains null pointer, null pointer) - */ - fun gcSearchPhantomBaby(disk: VirtualDisk): List> { - // Pair - val phantoms = ArrayList>() - disk.entries.filter { it.value.contents is EntryDirectory }.values.forEach { directory -> - (directory.contents as EntryDirectory).forEach { dirEntryID -> - if (disk.entries[dirEntryID] == null) { - phantoms.add(Pair(directory.entryID, dirEntryID)) - } - } - } - return phantoms - } - - fun gcDumpOrphans(disk: VirtualDisk) { - try { - gcSearchOrphan(disk).forEach { - disk.entries.remove(it) - } - } - catch (e: Exception) { - e.printStackTrace() - throw InternalError("Aw, snap!") - } - } - - fun gcDumpAll(disk: VirtualDisk) { - try { - gcSearchPhantomBaby(disk).forEach { - getAsDirectory(disk, it.first).remove(it.second) - } - gcSearchOrphan(disk).forEach { - disk.entries.remove(it) - } - } - catch (e: Exception) { - e.printStackTrace() - throw InternalError("Aw, snap!") - } - } -} - -fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this) -fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this) -fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean { - return !Arrays.equals(array, magic) -} -fun String.toEntryName(length: Int, charset: Charset): ByteArray { - val buffer = AppendableByteBuffer(length.toLong()) - val stringByteArray = this.toByteArray(charset) - buffer.put(stringByteArray.sliceArray(0..minOf(length, stringByteArray.size) - 1)) - return buffer.array.toByteArray() -} -fun ByteArray.toCanonicalString(charset: Charset): String { - var lastIndexOfRealStr = 0 - for (i in this.lastIndex downTo 0) { - if (this[i] != 0.toByte()) { - lastIndexOfRealStr = i - break - } - } - return String(this.sliceArray(0..lastIndexOfRealStr), charset) -} - -fun ArrayList.toByteArray64(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 { - val array = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(this.size.toLong()) - this.forEachIndexed { index, byte -> - array[index.toLong()] = byte - } - return array -} -fun ByteArray.toByteArray64(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 { - val array = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(this.size.toLong()) - this.forEachIndexed { index, byte -> - array[index.toLong()] = byte - } - return array -} - -/** - * Writes String to the file - * - * Note: this FileWriter cannot write more than 2 GiB - * - * @param fileEntry must be File, resolve symlink beforehand - * @param mode "w" or "a" - */ -class VDFileWriter(private val fileEntry: DiskEntry, private val append: Boolean, val charset: Charset) : Writer() { - - private @Volatile var newFileBuffer = ArrayList() - - private @Volatile var closed = false - - init { - if (fileEntry.contents !is EntryFile) { - throw FileNotFoundException("Not a file") - } - } - - override fun write(cbuf: CharArray, off: Int, len: Int) { - if (!closed) { - val newByteArray = String(cbuf).toByteArray(charset).toByteArray64() - newByteArray.forEach { newFileBuffer.add(it) } - } - else { - throw IOException() - } - } - - override fun flush() { - if (!closed) { - val newByteArray = newFileBuffer.toByteArray() - - if (!append) { - (fileEntry.contents as EntryFile).bytes = newByteArray.toByteArray64() - } - else { - val oldByteArray = (fileEntry.contents as EntryFile).bytes.toByteArray().copyOf() - val newFileBuffer = ByteArray(oldByteArray.size + newByteArray.size) - - System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size) - System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size) - - fileEntry.contents.bytes = newByteArray.toByteArray64() - } - - newFileBuffer = ArrayList() - - fileEntry.modificationDate = VDUtil.currentUnixtime - } - else { - throw IOException() - } - } - - override fun close() { - flush() - closed = true - } -} - -class VDFileOutputStream(private val fileEntry: DiskEntry, private val append: Boolean, val charset: Charset) : OutputStream() { - - private @Volatile var newFileBuffer = ArrayList() - - private @Volatile var closed = false - - override fun write(b: Int) { - if (!closed) { - newFileBuffer.add(b.toByte()) - } - else { - throw IOException() - } - } - - override fun flush() { - if (!closed) { - val newByteArray = newFileBuffer.toByteArray() - - if (!append) { - (fileEntry.contents as EntryFile).bytes = newByteArray.toByteArray64() - } - else { - val oldByteArray = (fileEntry.contents as EntryFile).bytes.toByteArray().copyOf() - val newFileBuffer = ByteArray(oldByteArray.size + newByteArray.size) - - System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size) - System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size) - - fileEntry.contents.bytes = newByteArray.toByteArray64() - } - - newFileBuffer = ArrayList() - - fileEntry.modificationDate = VDUtil.currentUnixtime - } - else { - throw IOException() - } - } - - override fun close() { - flush() - closed = true - } -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/VirtualDisk.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/VirtualDisk.kt deleted file mode 100644 index 1e5da85c1..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/VirtualDisk.kt +++ /dev/null @@ -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() - 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() // 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 = ArrayList()) : 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) -} diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/finder/Popups.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/finder/Popups.kt deleted file mode 100644 index af73025b6..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/finder/Popups.kt +++ /dev/null @@ -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) - } -} diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/finder/VirtualDiskCracker.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/finder/VirtualDiskCracker.kt deleted file mode 100644 index 723dfcac0..000000000 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/finder/VirtualDiskCracker.kt +++ /dev/null @@ -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? = null - private val directoryHierarchy = Stack(); 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) { - VirtualDiskCracker(Charset.forName("CP437")) -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/tvd_20170723.zip b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/tvd_20170723.zip deleted file mode 100644 index 88cae4a67..000000000 Binary files a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/tvd/tvd_20170723.zip and /dev/null differ diff --git a/src/net/torvald/terrarum/serialise/SavegameWriter.kt b/src/net/torvald/terrarum/serialise/SavegameWriter.kt index 5db37a616..d900dd4cc 100644 --- a/src/net/torvald/terrarum/serialise/SavegameWriter.kt +++ b/src/net/torvald/terrarum/serialise/SavegameWriter.kt @@ -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) + } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt b/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt index 73a0c565b..7b5b8bc7d 100644 --- a/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt +++ b/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt @@ -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() diff --git a/src/net/torvald/terrarum/serialise/WriteWorldInfo.kt b/src/net/torvald/terrarum/serialise/WriteWorldInfo.kt index 593e6abb5..253a7d767 100644 --- a/src/net/torvald/terrarum/serialise/WriteWorldInfo.kt +++ b/src/net/torvald/terrarum/serialise/WriteWorldInfo.kt @@ -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? { - val world = (Terrarum.ingame!!.world) + internal operator fun invoke(world: GameWorld): List? { - 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() - outFiles.add(metaFile) - + val outFiles = ArrayList() // for worldinfo1-3 only val worldInfoHash = ArrayList() // 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() } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/tests/GsonTest.kt b/src/net/torvald/terrarum/tests/GsonTest.kt new file mode 100644 index 000000000..d8304cc4a --- /dev/null +++ b/src/net/torvald/terrarum/tests/GsonTest.kt @@ -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) { + GsonTest.invoke() +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index 6fc1fab46..6c756abe1 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -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) diff --git a/src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt b/src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt index 3b38ce7f7..fd8ce138a 100644 --- a/src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt +++ b/src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt @@ -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() } diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt index 416377431..336e22089 100644 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt +++ b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt @@ -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) { diff --git a/work_files/DataFormats/Savegame container.txt b/work_files/DataFormats/Savegame container.txt index a352aa036..0cb887fd8 100644 --- a/work_files/DataFormats/Savegame container.txt +++ b/work_files/DataFormats/Savegame container.txt @@ -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 diff --git a/work_files/DataFormats/Savegame metadata.txt b/work_files/DataFormats/Savegame metadata.txt index 37aaa504c..b3ce69bd6 100644 --- a/work_files/DataFormats/Savegame metadata.txt +++ b/work_files/DataFormats/Savegame metadata.txt @@ -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)