mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
gson test in progress
This commit is contained in:
BIN
lib/TerranVirtualDisk.jar
Normal file
BIN
lib/TerranVirtualDisk.jar
Normal file
Binary file not shown.
BIN
lib/gson-2.5.jar
BIN
lib/gson-2.5.jar
Binary file not shown.
BIN
lib/gson-2.8.5.jar
Normal file
BIN
lib/gson-2.8.5.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
lib/javadoc/gson-2.8.5-javadoc.jar
Normal file
BIN
lib/javadoc/gson-2.8.5-javadoc.jar
Normal file
Binary file not shown.
BIN
lib/source/TerranVirtualDisk-src.jar
Normal file
BIN
lib/source/TerranVirtualDisk-src.jar
Normal file
Binary file not shown.
BIN
lib/source/gson-2.8.5-sources.jar
Normal file
BIN
lib/source/gson-2.8.5-sources.jar
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
@@ -31,11 +31,15 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
*
|
||||
* Most of the time it'd be the "player", but think about the case where you have possessed
|
||||
* some random actor of the game. Now that actor is now actorNowPlaying, the actual gamer's avatar
|
||||
* (reference ID of 0x91A7E2) (must) stay in the actorContainer, but it's not a actorNowPlaying.
|
||||
* (reference ID of 0x91A7E2) (must) stay in the actorContainerActive, but it's not a actorNowPlaying.
|
||||
*
|
||||
* Nullability of this property is believed to be unavoidable (trust me!). I'm sorry for the inconvenience.
|
||||
*/
|
||||
open var actorNowPlaying: ActorHumanoid? = null
|
||||
/**
|
||||
* The actual gamer
|
||||
*/
|
||||
open var actorGamer: ActorHumanoid? = null
|
||||
|
||||
open var gameInitialised = false
|
||||
internal set
|
||||
@@ -43,7 +47,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
internal set
|
||||
|
||||
val ACTORCONTAINER_INITIAL_SIZE = 64
|
||||
val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
||||
val actorContainerActive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
||||
val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
||||
|
||||
protected val terrainChangeQueue = Queue<BlockChangeQueueItem>()
|
||||
@@ -135,10 +139,10 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
///////////////////////
|
||||
|
||||
fun getActorByID(ID: Int): Actor {
|
||||
if (actorContainer.size == 0 && actorContainerInactive.size == 0)
|
||||
if (actorContainerActive.size == 0 && actorContainerInactive.size == 0)
|
||||
throw IllegalArgumentException("Actor with ID $ID does not exist.")
|
||||
|
||||
var index = actorContainer.binarySearch(ID)
|
||||
var index = actorContainerActive.binarySearch(ID)
|
||||
if (index < 0) {
|
||||
index = actorContainerInactive.binarySearch(ID)
|
||||
|
||||
@@ -154,7 +158,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
return actorContainerInactive[index]
|
||||
}
|
||||
else
|
||||
return actorContainer[index]
|
||||
return actorContainerActive[index]
|
||||
}
|
||||
|
||||
fun ArrayList<*>.binarySearch(actor: Actor) = this.binarySearch(actor.referenceID!!)
|
||||
@@ -191,9 +195,9 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
open fun removeActor(actor: Actor?) {
|
||||
if (actor == null) return
|
||||
|
||||
val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
|
||||
val indexToDelete = actorContainerActive.binarySearch(actor.referenceID!!)
|
||||
if (indexToDelete >= 0) {
|
||||
actorContainer.removeAt(indexToDelete)
|
||||
actorContainerActive.removeAt(indexToDelete)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,16 +211,16 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
throw Error("The actor $actor already exists in the game")
|
||||
}
|
||||
else {
|
||||
actorContainer.add(actor)
|
||||
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
|
||||
actorContainerActive.add(actor)
|
||||
insertionSortLastElem(actorContainerActive) // we can do this as we are only adding single actor
|
||||
}
|
||||
}
|
||||
|
||||
fun isActive(ID: Int): Boolean =
|
||||
if (actorContainer.size == 0)
|
||||
if (actorContainerActive.size == 0)
|
||||
false
|
||||
else
|
||||
actorContainer.binarySearch(ID) >= 0
|
||||
actorContainerActive.binarySearch(ID) >= 0
|
||||
|
||||
fun isInactive(ID: Int): Boolean =
|
||||
if (actorContainerInactive.size == 0)
|
||||
@@ -225,7 +229,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
actorContainerInactive.binarySearch(ID) >= 0
|
||||
|
||||
/**
|
||||
* actorContainer extensions
|
||||
* actorContainerActive extensions
|
||||
*/
|
||||
fun theGameHasActor(actor: Actor?) = if (actor == null) false else theGameHasActor(actor.referenceID!!)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -35,7 +35,7 @@ abstract class Actor(val renderOrder: RenderOrder) : Comparable<Actor>, Runnable
|
||||
* @return Reference ID. (16777216-0x7FFF_FFFF)
|
||||
*/
|
||||
open var referenceID: ActorID? = null
|
||||
var actorValue = ActorValue(this)
|
||||
var actorValue = ActorValue(this) // FIXME cyclic reference on GSON
|
||||
@Volatile var flagDespawn = false
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
@@ -5,7 +5,7 @@ import net.torvald.terrarum.KVHashMap
|
||||
/**
|
||||
* Created by minjaesong on 2017-04-28.
|
||||
*/
|
||||
class ActorValue(val actor: Actor) : KVHashMap() {
|
||||
class ActorValue(@Transient val actor: Actor) : KVHashMap() {
|
||||
|
||||
private constructor(actor: Actor, newMap: HashMap<String, Any>): this(actor) {
|
||||
hashMap = newMap
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,11 @@ abstract class GameItem : Comparable<GameItem>, Cloneable {
|
||||
abstract val isDynamic: Boolean
|
||||
|
||||
/**
|
||||
* Where to equip the item
|
||||
* Where to equip the item.
|
||||
*
|
||||
* Can't use 'open val' as GSON don't like that
|
||||
*/
|
||||
open val equipPosition: Int = EquipPosition.NULL
|
||||
var equipPosition: Int = EquipPosition.NULL
|
||||
|
||||
abstract val material: Material
|
||||
|
||||
@@ -273,6 +275,7 @@ abstract class GameItem : Comparable<GameItem>, Cloneable {
|
||||
|
||||
fun generateUniqueDynamicID(inventory: ActorInventory): GameItem {
|
||||
dynamicID = GameItem.generateUniqueDynamicID(inventory)
|
||||
ItemCodex.registerNewDynamicItem(dynamicID, this)
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -285,6 +288,7 @@ abstract class GameItem : Comparable<GameItem>, Cloneable {
|
||||
do {
|
||||
ret = ITEM_DYNAMIC.pickRandom()
|
||||
} while (inventory.contains(ret))
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package net.torvald.terrarum.itemproperties
|
||||
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import net.torvald.terrarum.KVHashMap
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.blockproperties.Fluid
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
@@ -21,7 +22,7 @@ object ItemCodex {
|
||||
* Will return corresponding Actor if ID >= ACTORID_MIN
|
||||
*/
|
||||
val itemCodex = HashMap<ItemID, GameItem>()
|
||||
private val dynamicItemDescription = HashMap<ItemID, KVHashMap>()
|
||||
val dynamicItemDescription = HashMap<ItemID, GameItem>()
|
||||
|
||||
val ITEM_TILES = 0..GameWorld.TILES_SUPPORTED - 1
|
||||
val ITEM_WALLS = GameWorld.TILES_SUPPORTED..GameWorld.TILES_SUPPORTED * 2 - 1
|
||||
@@ -73,7 +74,7 @@ object ItemCodex {
|
||||
|
||||
// check for collision with actors (BLOCK only)
|
||||
if (this.inventoryCategory == Category.BLOCK) {
|
||||
ingame.actorContainer.forEach {
|
||||
ingame.actorContainerActive.forEach {
|
||||
if (it is ActorWBMovable && it.hIntTilewiseHitbox.intersects(mousePoint))
|
||||
return false
|
||||
}
|
||||
@@ -141,7 +142,7 @@ object ItemCodex {
|
||||
|
||||
// linear search filter (check for intersection with tilewise mouse point and tilewise hitbox)
|
||||
// return false if hitting actors
|
||||
ingame.actorContainer.forEach {
|
||||
ingame.actorContainerActive.forEach {
|
||||
if (it is ActorWBMovable && it.hIntTilewiseHitbox.intersects(mousePoint))
|
||||
return false
|
||||
}
|
||||
@@ -188,7 +189,9 @@ object ItemCodex {
|
||||
override val isDynamic: Boolean = false
|
||||
override val material: Material = Material(1,1,1,1,1,1,1,1,1,1.0)
|
||||
|
||||
override val equipPosition: Int = EquipPosition.HAND_GRIP
|
||||
init {
|
||||
equipPosition = EquipPosition.HAND_GRIP
|
||||
}
|
||||
|
||||
override fun startPrimaryUse(delta: Float): Boolean {
|
||||
val ingame = Terrarum.ingame!! as Ingame // must be in here
|
||||
@@ -215,7 +218,9 @@ object ItemCodex {
|
||||
override val isDynamic: Boolean = false
|
||||
override val material: Material = Material(1,1,1,1,1,1,1,1,1,1.0)
|
||||
|
||||
override val equipPosition: Int = EquipPosition.HAND_GRIP
|
||||
init {
|
||||
equipPosition = EquipPosition.HAND_GRIP
|
||||
}
|
||||
|
||||
override fun startPrimaryUse(delta: Float): Boolean {
|
||||
val ingame = Terrarum.ingame!! as Ingame // must be in here
|
||||
@@ -232,14 +237,22 @@ object ItemCodex {
|
||||
println()
|
||||
}
|
||||
|
||||
fun registerNewDynamicItem(dynamicID: Int, item: GameItem) {
|
||||
if (AppLoader.IS_DEVELOPMENT_BUILD) {
|
||||
printdbg(this, "Registering new dynamic item $dynamicID (from ${item.originalID})")
|
||||
}
|
||||
dynamicItemDescription[dynamicID] = item
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns clone of the item in the Codex
|
||||
* Returns the item in the Codex. If the item is static, its clone will be returned (you are free to modify the returned item).
|
||||
* However, if the item is dynamic, the item itself will be returned. Modifying the item will affect the game.
|
||||
*/
|
||||
operator fun get(code: ItemID): GameItem {
|
||||
if (code <= ITEM_STATIC.endInclusive) // generic item
|
||||
return itemCodex[code]!!.clone() // from CSV
|
||||
else if (code <= ITEM_DYNAMIC.endInclusive) {
|
||||
TODO("read from dynamicitem description (JSON)")
|
||||
return itemCodex[code]!!
|
||||
}
|
||||
else {
|
||||
val a = (Terrarum.ingame!! as Ingame).getActorByID(code) // actor item
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
*/
|
||||
//val ACTORCONTAINER_INITIAL_SIZE = 64
|
||||
val PARTICLES_MAX = AppLoader.getConfigInt("maxparticles")
|
||||
//val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
||||
//val actorContainerActive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
||||
//val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
||||
val particlesContainer = CircularArray<ParticleBase>(PARTICLES_MAX)
|
||||
val uiContainer = ArrayList<UICanvas>()
|
||||
@@ -156,6 +156,12 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
lateinit var gameworld: GameWorldExtension
|
||||
lateinit var theRealGamer: IngamePlayer
|
||||
|
||||
override var actorGamer: ActorHumanoid?
|
||||
get() = theRealGamer
|
||||
set(value) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
enum class GameLoadMode {
|
||||
CREATE_NEW, LOAD_FROM
|
||||
}
|
||||
@@ -611,10 +617,10 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
* if the actor is not to be dormant, it will be just ignored.
|
||||
*/
|
||||
fun KillOrKnockdownActors() {
|
||||
var actorContainerSize = actorContainer.size
|
||||
var actorContainerSize = actorContainerActive.size
|
||||
var i = 0
|
||||
while (i < actorContainerSize) { // loop through actorContainer
|
||||
val actor = actorContainer[i]
|
||||
while (i < actorContainerSize) { // loop through actorContainerActive
|
||||
val actor = actorContainerActive[i]
|
||||
val actorIndex = i
|
||||
// kill actors flagged to despawn
|
||||
if (actor.flagDespawn) {
|
||||
@@ -627,7 +633,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
if (actor !is Projectile) { // if it's a projectile, don't inactivate it; just kill it.
|
||||
actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated
|
||||
}
|
||||
actorContainer.removeAt(actorIndex)
|
||||
actorContainerActive.removeAt(actorIndex)
|
||||
actorContainerSize -= 1
|
||||
i-- // array removed 1 elem, so we also decrement counter by 1
|
||||
}
|
||||
@@ -641,8 +647,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
* NOTE: concurrency for actor updating is currently disabled because of it's poor performance
|
||||
*/
|
||||
fun updateActors(delta: Float) {
|
||||
if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD && actorContainer.size > Terrarum.THREADS) {
|
||||
val actors = actorContainer.size.toFloat()
|
||||
if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD && actorContainerActive.size > Terrarum.THREADS) {
|
||||
val actors = actorContainerActive.size.toFloat()
|
||||
// set up indices
|
||||
for (i in 0..Terrarum.THREADS - 1) {
|
||||
ThreadParallel.map(
|
||||
@@ -659,7 +665,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
actorNowPlaying?.update(delta)
|
||||
}
|
||||
else {
|
||||
actorContainer.forEach {
|
||||
actorContainerActive.forEach {
|
||||
if (it != actorNowPlaying) {
|
||||
it.update(delta)
|
||||
|
||||
@@ -736,12 +742,12 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
|
||||
if (actor.referenceID == theRealGamer.referenceID || actor.referenceID == 0x51621D) // do not delete this magic
|
||||
throw RuntimeException("Attempted to remove player.")
|
||||
val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
|
||||
val indexToDelete = actorContainerActive.binarySearch(actor.referenceID!!)
|
||||
if (indexToDelete >= 0) {
|
||||
printdbg(this, "Removing actor $actor")
|
||||
printStackTrace()
|
||||
|
||||
actorContainer.removeAt(indexToDelete)
|
||||
actorContainerActive.removeAt(indexToDelete)
|
||||
|
||||
// indexToDelete >= 0 means that the actor certainly exists in the game
|
||||
// which means we don't need to check if i >= 0 again
|
||||
@@ -785,8 +791,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
printdbg(this, "Adding actor $actor")
|
||||
printStackTrace()
|
||||
|
||||
actorContainer.add(actor)
|
||||
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
|
||||
actorContainerActive.add(actor)
|
||||
insertionSortLastElem(actorContainerActive) // we can do this as we are only adding single actor
|
||||
|
||||
if (actor is ActorWithBody) {
|
||||
when (actor.renderOrder) {
|
||||
@@ -819,8 +825,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
}
|
||||
else {
|
||||
actorContainerInactive.remove(actor)
|
||||
actorContainer.add(actor)
|
||||
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
|
||||
actorContainerActive.add(actor)
|
||||
insertionSortLastElem(actorContainerActive) // we can do this as we are only adding single actor
|
||||
|
||||
if (actor is ActorWithBody) {
|
||||
when (actor.renderOrder) {
|
||||
@@ -959,7 +965,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
|
||||
private fun printStackTrace() {
|
||||
Thread.currentThread().getStackTrace().forEach {
|
||||
printdbg(this, "-> $it")
|
||||
printdbg(this, "--> $it")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ internal object ActorsList : ConsoleCommand {
|
||||
|
||||
override fun execute(args: Array<String>) {
|
||||
jPanelInstances.add(ActorsLister(
|
||||
(Terrarum.ingame!! as Ingame).actorContainer,
|
||||
(Terrarum.ingame!! as Ingame).actorContainerActive,
|
||||
(Terrarum.ingame!! as Ingame).actorContainerInactive)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,13 @@ package net.torvald.terrarum.modulebasegame.console
|
||||
|
||||
import net.torvald.terrarum.console.ConsoleCommand
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.console.EchoError
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataLzma
|
||||
import net.torvald.terrarum.serialise.WriteLayerDataZip
|
||||
import net.torvald.terrarum.serialise.WriteWorldInfo
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-07-18.
|
||||
*/
|
||||
object ExportLayerData : ConsoleCommand {
|
||||
override fun execute(args: Array<String>) {
|
||||
try {
|
||||
/*try {
|
||||
val outfile = WriteLayerDataZip()
|
||||
WriteWorldInfo()
|
||||
Echo("Layer data exported to ${outfile!!.canonicalPath}")
|
||||
@@ -20,7 +16,7 @@ object ExportLayerData : ConsoleCommand {
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
EchoError("Layer data export failed; see console for error traces.")
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun printUsage() {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package net.torvald.terrarum.modulebasegame.console
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.console.ConsoleCommand
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
|
||||
import java.io.BufferedWriter
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
@@ -17,9 +16,25 @@ import java.io.IOException
|
||||
internal object GsonTest : ConsoleCommand {
|
||||
override fun execute(args: Array<String>) {
|
||||
if (args.size == 2) {
|
||||
val avelem = Gson().toJsonTree((Terrarum.ingame!! as Ingame).actorNowPlaying)
|
||||
|
||||
val jsonString = avelem.toString()
|
||||
val jsonBuilder = if (AppLoader.IS_DEVELOPMENT_BUILD) {
|
||||
GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
|
||||
.serializeNulls()
|
||||
.create()
|
||||
}
|
||||
else {
|
||||
GsonBuilder()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
}
|
||||
|
||||
|
||||
val jsonString = jsonBuilder.toJson((Terrarum.ingame!! as Ingame).actorNowPlaying)
|
||||
|
||||
//val avelem = Gson().toJson((Terrarum.ingame!! as Ingame).actorNowPlaying)
|
||||
//val jsonString = avelem.toString()
|
||||
|
||||
val bufferedWriter: BufferedWriter
|
||||
val writer: FileWriter
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,14 +20,16 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
* Created by minjaesong on 2016-03-15.
|
||||
*/
|
||||
|
||||
class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode: Int) {
|
||||
class ActorInventory(@Transient val actor: Pocketed, var maxCapacity: Int, var capacityMode: Int) {
|
||||
|
||||
companion object {
|
||||
@Transient val CAPACITY_MODE_NO_ENCUMBER = 0
|
||||
@Transient val CAPACITY_MODE_COUNT = 1
|
||||
@Transient val CAPACITY_MODE_WEIGHT = 2
|
||||
val CAPACITY_MODE_NO_ENCUMBER = 0
|
||||
val CAPACITY_MODE_COUNT = 1
|
||||
val CAPACITY_MODE_WEIGHT = 2
|
||||
}
|
||||
|
||||
// FIXME unless absolutely necessary, don't store full item object; only store its dynamicID
|
||||
|
||||
/**
|
||||
* List of all equipped items (tools, armours, rings, necklaces, etc.)
|
||||
*/
|
||||
@@ -265,8 +267,8 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
|
||||
arr[j + 1] = x
|
||||
}
|
||||
}
|
||||
private val STATIC_ID = 41324534
|
||||
private val DYNAMIC_ID = 181643953
|
||||
@Transient private val STATIC_ID = 41324534
|
||||
@Transient private val DYNAMIC_ID = 181643953
|
||||
private fun ArrayList<InventoryPair>.binarySearch(ID: ItemID, searchBy: Int): Int {
|
||||
// code from collections/Collections.kt
|
||||
var low = 0
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* ByteArray that can hold larger than 2 GiB of Data.
|
||||
*
|
||||
* Works kind of like Bank Switching of old game console's cartridges which does same thing.
|
||||
*
|
||||
* Created by Minjaesong on 2017-04-12.
|
||||
*/
|
||||
class ByteArray64(val size: Long) {
|
||||
companion object {
|
||||
val bankSize: Int = 8192
|
||||
}
|
||||
|
||||
internal val __data: Array<ByteArray>
|
||||
|
||||
init {
|
||||
if (size < 0)
|
||||
throw IllegalArgumentException("Invalid array size!")
|
||||
|
||||
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
|
||||
|
||||
__data = Array<ByteArray>(
|
||||
requiredBanks,
|
||||
{ bankIndex ->
|
||||
kotlin.ByteArray(
|
||||
if (bankIndex == requiredBanks - 1)
|
||||
size.toBankOffset()
|
||||
else
|
||||
bankSize,
|
||||
|
||||
{ 0.toByte() }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun Long.toBankNumber(): Int = (this / bankSize).toInt()
|
||||
private fun Long.toBankOffset(): Int = (this % bankSize).toInt()
|
||||
|
||||
operator fun set(index: Long, value: Byte) {
|
||||
if (index < 0 || index >= size)
|
||||
throw ArrayIndexOutOfBoundsException("size $size, index $index")
|
||||
|
||||
__data[index.toBankNumber()][index.toBankOffset()] = value
|
||||
}
|
||||
|
||||
operator fun get(index: Long): Byte {
|
||||
if (index < 0 || index >= size)
|
||||
throw ArrayIndexOutOfBoundsException("size $size, index $index")
|
||||
|
||||
return __data[index.toBankNumber()][index.toBankOffset()]
|
||||
}
|
||||
|
||||
operator fun iterator(): ByteIterator {
|
||||
return object : ByteIterator() {
|
||||
var iterationCounter = 0L
|
||||
|
||||
override fun nextByte(): Byte {
|
||||
iterationCounter += 1
|
||||
return this@ByteArray64[iterationCounter - 1]
|
||||
}
|
||||
|
||||
override fun hasNext() = iterationCounter < this@ByteArray64.size
|
||||
}
|
||||
}
|
||||
|
||||
fun iteratorChoppedToInt(): IntIterator {
|
||||
return object : IntIterator() {
|
||||
var iterationCounter = 0L
|
||||
val iteratorSize = 1 + ((this@ByteArray64.size - 1) / 4).toInt()
|
||||
|
||||
override fun nextInt(): Int {
|
||||
var byteCounter = iterationCounter * 4L
|
||||
var int = 0
|
||||
(0..3).forEach {
|
||||
if (byteCounter + it < this@ByteArray64.size) {
|
||||
int += this@ByteArray64[byteCounter + it].toInt() shl (it * 8)
|
||||
}
|
||||
else {
|
||||
int += 0 shl (it * 8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
iterationCounter += 1
|
||||
return int
|
||||
}
|
||||
|
||||
override fun hasNext() = iterationCounter < iteratorSize
|
||||
}
|
||||
}
|
||||
|
||||
fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
|
||||
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) }
|
||||
fun forEachBanks(consumer: (ByteArray) -> Unit) = __data.forEach(consumer)
|
||||
|
||||
fun sliceArray64(range: LongRange): ByteArray64 {
|
||||
val newarr = ByteArray64(range.last - range.first + 1)
|
||||
range.forEach { index ->
|
||||
newarr[index - range.first] = this[index]
|
||||
}
|
||||
return newarr
|
||||
}
|
||||
|
||||
fun sliceArray(range: IntRange): ByteArray {
|
||||
val newarr = ByteArray(range.last - range.first + 1)
|
||||
range.forEach { index ->
|
||||
newarr[index - range.first] = this[index.toLong()]
|
||||
}
|
||||
return newarr
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
|
||||
throw TypeCastException("Impossible cast; too large to fit")
|
||||
|
||||
return ByteArray(this.size.toInt(), { this[it.toLong()] })
|
||||
}
|
||||
|
||||
fun writeToFile(file: File) {
|
||||
var fos = FileOutputStream(file, false)
|
||||
fos.write(__data[0])
|
||||
fos.flush()
|
||||
fos.close()
|
||||
|
||||
if (__data.size > 1) {
|
||||
fos = FileOutputStream(file, true)
|
||||
for (i in 1..__data.lastIndex) {
|
||||
fos.write(__data[i])
|
||||
fos.flush()
|
||||
}
|
||||
fos.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class ByteArray64InputStream(val byteArray64: ByteArray64): InputStream() {
|
||||
protected open var readCounter = 0L
|
||||
|
||||
override fun read(): Int {
|
||||
readCounter += 1
|
||||
|
||||
return try {
|
||||
byteArray64[readCounter - 1].toUint()
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Static ByteArray OutputStream. Less leeway, more stable. */
|
||||
open class ByteArray64OutputStream(val byteArray64: ByteArray64): OutputStream() {
|
||||
protected open var writeCounter = 0L
|
||||
|
||||
override fun write(b: Int) {
|
||||
try {
|
||||
writeCounter += 1
|
||||
|
||||
byteArray64[writeCounter - 1] = b.toByte()
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Just like Java's ByteArrayOutputStream, except DON'T TRY TO GROW THE BUFFER */
|
||||
open class ByteArray64GrowableOutputStream(val size: Long = ByteArray64.bankSize.toLong()): OutputStream() {
|
||||
protected open var buf = ByteArray64(size)
|
||||
protected open var count = 0L
|
||||
|
||||
override fun write(b: Int) {
|
||||
ensureCapacity(count + 1)
|
||||
buf[count] = b.toByte()
|
||||
count += 1
|
||||
}
|
||||
|
||||
private fun ensureCapacity(minCapacity: Long) {
|
||||
// overflow-conscious code
|
||||
if (minCapacity - buf.size > 0)
|
||||
grow(minCapacity)
|
||||
}
|
||||
|
||||
private fun grow(minCapacity: Long) {
|
||||
// overflow-conscious code
|
||||
val oldCapacity = buf.size
|
||||
var newCapacity = oldCapacity shl 1
|
||||
if (newCapacity - minCapacity < 0)
|
||||
newCapacity = minCapacity
|
||||
// double the capacity
|
||||
val newBuffer = ByteArray64(buf.size * 2)
|
||||
buf.__data.forEachIndexed { index, bytes ->
|
||||
System.arraycopy(
|
||||
buf.__data[index], 0,
|
||||
newBuffer.__data[index], 0, buf.__data.size
|
||||
)
|
||||
}
|
||||
buf = newBuffer
|
||||
System.gc()
|
||||
}
|
||||
|
||||
/** Unlike Java's, this does NOT create a copy of the internal buffer; this just returns its internal. */
|
||||
@Synchronized
|
||||
fun toByteArray64(): ByteArray64 {
|
||||
return buf
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||
|
||||
import net.torvald.terrarum.serialise.toLittleInt
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
|
||||
*
|
||||
* Created by minjaesong on 2017-11-17.
|
||||
*/
|
||||
class DiskSkimmer(private val diskFile: File) {
|
||||
|
||||
|
||||
/**
|
||||
* EntryID to Offset.
|
||||
*
|
||||
* Offset is where the header begins, so first 4 bytes are exactly the same as the EntryID.
|
||||
*/
|
||||
val entryToOffsetTable = HashMap<EntryID, Long>()
|
||||
|
||||
|
||||
init {
|
||||
val fis = FileInputStream(diskFile)
|
||||
var currentPosition = fis.skip(47) // skip disk header
|
||||
|
||||
|
||||
fun skipRead(bytes: Long) {
|
||||
currentPosition += fis.skip(bytes)
|
||||
}
|
||||
/**
|
||||
* Reads a byte and adds up the position var
|
||||
*/
|
||||
fun readByte(): Byte {
|
||||
currentPosition++
|
||||
val read = fis.read()
|
||||
|
||||
if (read < 0) throw InternalError("Unexpectedly reached EOF")
|
||||
return read.toByte()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads specific bytes to the buffer and adds up the position var
|
||||
*/
|
||||
fun readBytes(buffer: ByteArray): Int {
|
||||
val readStatus = fis.read(buffer)
|
||||
currentPosition += readStatus
|
||||
return readStatus
|
||||
}
|
||||
fun readIntBig(): Int {
|
||||
val buffer = ByteArray(4)
|
||||
val readStatus = readBytes(buffer)
|
||||
if (readStatus != 4) throw InternalError("Unexpected error -- EOF reached? (expected 4, got $readStatus)")
|
||||
return buffer.toIntBig()
|
||||
}
|
||||
fun readInt48(): Long {
|
||||
val buffer = ByteArray(6)
|
||||
val readStatus = readBytes(buffer)
|
||||
if (readStatus != 6) throw InternalError("Unexpected error -- EOF reached? (expected 6, got $readStatus)")
|
||||
return buffer.toInt48()
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
val entryID = readIntBig()
|
||||
|
||||
// footer
|
||||
if (entryID == 0xFEFEFEFE.toInt()) break
|
||||
|
||||
|
||||
// fill up table
|
||||
entryToOffsetTable[entryID] = currentPosition
|
||||
|
||||
skipRead(4) // skip entryID of parent
|
||||
|
||||
val entryType = readByte()
|
||||
|
||||
skipRead(256 + 6 + 6 + 4) // skips rest of the header
|
||||
|
||||
|
||||
// figure out the entry size so that we can skip
|
||||
val entrySize: Long = when(entryType) {
|
||||
0x01.toByte() -> readInt48()
|
||||
0x11.toByte() -> readInt48() + 6 // size of compressed payload + 6 (header elem for uncompressed size)
|
||||
0x02.toByte() -> readIntBig().shl(16).toLong() * 4 - 2 // #entris is 2 bytes, we read 4 bytes, so we subtract 2
|
||||
0x03.toByte() -> 4 // symlink
|
||||
else -> throw InternalError("Unknown entry type: ${entryType.toUint()}")
|
||||
}
|
||||
|
||||
|
||||
skipRead(entrySize) // skips rest of the entry's actual contents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Using entryToOffsetTable, composes DiskEntry on the fly upon request.
|
||||
* @return DiskEntry if the entry exists on the disk, `null` otherwise.
|
||||
*/
|
||||
fun requestFile(entryID: EntryID): DiskEntry? {
|
||||
// FIXME untested
|
||||
entryToOffsetTable[entryID].let { offset ->
|
||||
if (offset == null)
|
||||
return null
|
||||
else {
|
||||
val fis = FileInputStream(diskFile)
|
||||
fis.skip(offset + 4) // get to the EntryHeader's parent directory area
|
||||
val parent = fis.read(4).toLittleInt()
|
||||
val fileFlag = fis.read(1)[0]
|
||||
val filename = fis.read(256)
|
||||
val creationTime = fis.read(6).toInt48()
|
||||
val modifyTime = fis.read(6).toInt48()
|
||||
val skip_crc = fis.read(4)
|
||||
|
||||
// get entry size // TODO future me, does this kind of comment helpful or redundant?
|
||||
val entrySize = when (fileFlag) {
|
||||
DiskEntry.NORMAL_FILE -> {
|
||||
fis.read(6).toInt48()
|
||||
}
|
||||
DiskEntry.DIRECTORY -> {
|
||||
fis.read(2).toUint16().toLong()
|
||||
}
|
||||
DiskEntry.SYMLINK -> 4L
|
||||
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
|
||||
}
|
||||
|
||||
|
||||
val entryContent = when (fileFlag) {
|
||||
DiskEntry.NORMAL_FILE -> {
|
||||
val byteArray = ByteArray64(entrySize)
|
||||
// read one byte at a time
|
||||
for (c in 0L until entrySize) {
|
||||
byteArray[c] = fis.read().toByte()
|
||||
}
|
||||
|
||||
EntryFile(byteArray)
|
||||
}
|
||||
DiskEntry.DIRECTORY -> {
|
||||
val dirContents = ArrayList<EntryID>()
|
||||
// read 4 bytes at a time
|
||||
val bytesBuffer4 = ByteArray(4)
|
||||
for (c in 0L until entrySize) {
|
||||
fis.read(bytesBuffer4)
|
||||
dirContents.add(bytesBuffer4.toIntBig())
|
||||
}
|
||||
|
||||
EntryDirectory(dirContents)
|
||||
}
|
||||
DiskEntry.SYMLINK -> {
|
||||
val target = fis.read(4).toIntBig()
|
||||
|
||||
EntrySymlink(target)
|
||||
}
|
||||
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
|
||||
}
|
||||
|
||||
return DiskEntry(entryID, parent, filename, creationTime, modifyTime, entryContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Only use it when you're sure you won't reach EOF; unavailable cells in array will be filled with -1. */
|
||||
fun InputStream.read(size: Int): ByteArray {
|
||||
val ba = ByteArray(size)
|
||||
this.read(ba)
|
||||
return ba
|
||||
}
|
||||
}
|
||||
|
||||
private fun ByteArray.toUint16(): Int {
|
||||
return this[0].toUint().shl(8) or
|
||||
this[1].toUint()
|
||||
}
|
||||
|
||||
private fun ByteArray.toIntBig(): Int {
|
||||
return this[0].toUint().shl(24) or
|
||||
this[1].toUint().shl(16) or
|
||||
this[2].toUint().shl(8) or
|
||||
this[3].toUint()
|
||||
}
|
||||
|
||||
private fun ByteArray.toInt48(): Long {
|
||||
return this[0].toUlong().shl(40) or
|
||||
this[1].toUlong().shl(32) or
|
||||
this[2].toUlong().shl(24) or
|
||||
this[3].toUlong().shl(16) or
|
||||
this[4].toUlong().shl(8) or
|
||||
this[5].toUlong()
|
||||
}
|
||||
|
||||
private fun ByteArray.toInt64(): Long {
|
||||
return this[0].toUlong().shl(56) or
|
||||
this[1].toUlong().shl(48) or
|
||||
this[2].toUlong().shl(40) or
|
||||
this[3].toUlong().shl(32) or
|
||||
this[4].toUlong().shl(24) or
|
||||
this[5].toUlong().shl(16) or
|
||||
this[6].toUlong().shl(8) or
|
||||
this[7].toUlong()
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
# Terran Virtual Disk Image Format Specification
|
||||
|
||||
current specversion number: 0x03
|
||||
|
||||
## Changes
|
||||
|
||||
### 0x03
|
||||
- Option to compress file entry
|
||||
|
||||
### 0x02
|
||||
- 48-Bit filesize and timestamp (Max 256 TiB / 8.9 million years)
|
||||
- 8 Reserved footer
|
||||
|
||||
### 0x01
|
||||
**Note: this version were never released in public**
|
||||
- Doubly Linked List instead of Singly
|
||||
|
||||
|
||||
## Specs
|
||||
|
||||
* File structure
|
||||
|
||||
|
||||
Header
|
||||
|
||||
IndexNumber
|
||||
<entry>
|
||||
|
||||
IndexNumber
|
||||
<entry>
|
||||
|
||||
IndexNumber
|
||||
<entry>
|
||||
|
||||
...
|
||||
|
||||
Footer
|
||||
|
||||
|
||||
* Order of the indices does not matter. Actual sorting is a job of the application.
|
||||
* Endianness: Big
|
||||
|
||||
|
||||
## Header
|
||||
Uint8[4] Magic: TEVd
|
||||
Int48 Disk size in bytes (max 256 TiB)
|
||||
Uint8[32] Disk name
|
||||
Int32 CRC-32
|
||||
1. create list of arrays that contains CRC
|
||||
2. put all the CRCs of entries
|
||||
3. sort the list (here's the catch -- you will treat CRCs as SIGNED integer)
|
||||
4. for elems on list: update crc with the elem (crc = calculateCRC(crc, elem))
|
||||
Int8 Version
|
||||
|
||||
(Header size: 47 bytes)
|
||||
|
||||
|
||||
|
||||
## IndexNumber and Contents
|
||||
<Entry Header>
|
||||
<Actual Entry>
|
||||
|
||||
### Entry Header
|
||||
Int32 EntryID (random Integer). This act as "jump" position for directory listing.
|
||||
NOTE: Index 0 must be a root "Directory"; 0xFEFEFEFE is invalid (used as footer marker)
|
||||
Int32 EntryID of parent directory
|
||||
Int8 Flag for file or directory or symlink (cannot be negative)
|
||||
0x01: Normal file, 0x02: Directory list, 0x03: Symlink
|
||||
0x11: Compressed normal file
|
||||
Uint8[256] File name in UTF-8
|
||||
Int48 Creation date in real-life UNIX timestamp
|
||||
Int48 Last modification date in real-life UNIX timestamp
|
||||
Int32 CRC-32 of Actual Entry
|
||||
|
||||
(Header size: 281 bytes)
|
||||
|
||||
### Entry of File (Uncompressed)
|
||||
Int48 File size in bytes (max 256 TiB)
|
||||
<Bytes> Actual Contents
|
||||
|
||||
(Header size: 6 bytes)
|
||||
|
||||
### Entry of File (Compressed)
|
||||
Int48 Size of compressed payload (max 256 TiB)
|
||||
Int48 Size of uncompressed file (max 256 TiB)
|
||||
<Bytes> Actual Contents, DEFLATEd payload
|
||||
|
||||
(Header size: 12 bytes)
|
||||
|
||||
### Entry of Directory
|
||||
Uint16 Number of entries (normal files, other directories, symlinks)
|
||||
<Int32s> Entry listing, contains IndexNumber
|
||||
|
||||
(Header size: 2 bytes)
|
||||
|
||||
### Entry of Symlink
|
||||
Int32 Target IndexNumber
|
||||
|
||||
(Content size: 4 bytes)
|
||||
|
||||
|
||||
|
||||
|
||||
## Footer
|
||||
Uint8[4] 0xFE 0xFE 0xFE 0xFE (footer marker)
|
||||
Int8 Disk properties flag 1
|
||||
0b 7 6 5 4 3 2 1 0
|
||||
|
||||
0th bit: Readonly
|
||||
|
||||
Int8[7] Reserved, should be filled with zero
|
||||
<optional footer if present>
|
||||
Uint8[2] 0xFF 0x19 (EOF mark)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,307 +0,0 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.or
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-03-31.
|
||||
*/
|
||||
|
||||
typealias EntryID = Int
|
||||
|
||||
val specversion = 0x03.toByte()
|
||||
|
||||
class VirtualDisk(
|
||||
/** capacity of 0 makes the disk read-only */
|
||||
var capacity: Long,
|
||||
var diskName: ByteArray = ByteArray(NAME_LENGTH),
|
||||
footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(8) // default to mandatory 8-byte footer
|
||||
) {
|
||||
var footerBytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = footer
|
||||
private set
|
||||
val entries = HashMap<EntryID, DiskEntry>()
|
||||
var isReadOnly: Boolean
|
||||
set(value) { footerBytes[0] = (footerBytes[0] and 0xFE.toByte()) or value.toBit() }
|
||||
get() = capacity == 0L || (footerBytes.size > 0 && footerBytes[0].and(1) == 1.toByte())
|
||||
fun getDiskNameString(charset: Charset) = String(diskName, charset)
|
||||
val root: DiskEntry
|
||||
get() = entries[0]!!
|
||||
|
||||
|
||||
private fun Boolean.toBit() = if (this) 1.toByte() else 0.toByte()
|
||||
|
||||
internal fun __internalSetFooter__(footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) {
|
||||
footerBytes = footer
|
||||
}
|
||||
|
||||
private fun serializeEntriesOnly(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 {
|
||||
val bufferList = ArrayList<Byte>() // FIXME this part would take up excessive memory for large files
|
||||
entries.forEach {
|
||||
val serialised = it.value.serialize()
|
||||
serialised.forEach { bufferList.add(it) }
|
||||
}
|
||||
|
||||
val byteArray = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(bufferList.size.toLong())
|
||||
bufferList.forEachIndexed { index, byte -> byteArray[index.toLong()] = byte }
|
||||
return byteArray
|
||||
}
|
||||
|
||||
fun serialize(): AppendableByteBuffer {
|
||||
val entriesBuffer = serializeEntriesOnly()
|
||||
val buffer = AppendableByteBuffer(HEADER_SIZE + entriesBuffer.size + FOOTER_SIZE + footerBytes.size)
|
||||
val crc = hashCode().toBigEndian()
|
||||
|
||||
buffer.put(MAGIC)
|
||||
buffer.put(capacity.toInt48())
|
||||
buffer.put(diskName.forceSize(NAME_LENGTH))
|
||||
buffer.put(crc)
|
||||
buffer.put(specversion)
|
||||
buffer.put(entriesBuffer)
|
||||
buffer.put(FOOTER_START_MARK)
|
||||
buffer.put(footerBytes)
|
||||
buffer.put(EOF_MARK)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val crcList = IntArray(entries.size)
|
||||
var crcListAppendCursor = 0
|
||||
entries.forEach { _, u ->
|
||||
crcList[crcListAppendCursor] = u.hashCode()
|
||||
crcListAppendCursor++
|
||||
}
|
||||
crcList.sort()
|
||||
val crc = CRC32()
|
||||
crcList.forEach { crc.update(it) }
|
||||
|
||||
return crc.value.toInt()
|
||||
}
|
||||
|
||||
/** Expected size of the virtual disk */
|
||||
val usedBytes: Long
|
||||
get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE
|
||||
|
||||
fun generateUniqueID(): Int {
|
||||
var id: Int
|
||||
do {
|
||||
id = Random().nextInt()
|
||||
} while (null != entries[id] || id == FOOTER_MARKER)
|
||||
return id
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
|
||||
override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})"
|
||||
|
||||
companion object {
|
||||
val HEADER_SIZE = 47L // according to the spec
|
||||
val FOOTER_SIZE = 6L // footer mark + EOF
|
||||
val NAME_LENGTH = 32
|
||||
|
||||
val MAGIC = "TEVd".toByteArray()
|
||||
val FOOTER_MARKER = 0xFEFEFEFE.toInt()
|
||||
val FOOTER_START_MARK = FOOTER_MARKER.toBigEndian()
|
||||
val EOF_MARK = byteArrayOf(0xFF.toByte(), 0x19.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DiskEntry(
|
||||
// header
|
||||
var entryID: EntryID,
|
||||
var parentEntryID: EntryID,
|
||||
var filename: ByteArray = ByteArray(NAME_LENGTH),
|
||||
var creationDate: Long,
|
||||
var modificationDate: Long,
|
||||
|
||||
// content
|
||||
val contents: DiskEntryContent
|
||||
) {
|
||||
fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset)
|
||||
|
||||
val serialisedSize: Long
|
||||
get() = contents.getSizeEntry() + HEADER_SIZE
|
||||
|
||||
companion object {
|
||||
val HEADER_SIZE = 281L // according to the spec
|
||||
val ROOTNAME = "(root)"
|
||||
val NAME_LENGTH = 256
|
||||
|
||||
val NORMAL_FILE = 1.toByte()
|
||||
val DIRECTORY = 2.toByte()
|
||||
val SYMLINK = 3.toByte()
|
||||
val COMPRESSED_FILE = 0x11.toByte()
|
||||
|
||||
private fun DiskEntryContent.getTypeFlag() =
|
||||
if (this is EntryFile) NORMAL_FILE
|
||||
else if (this is EntryDirectory) DIRECTORY
|
||||
else if (this is EntrySymlink) SYMLINK
|
||||
else 0 // NULL
|
||||
|
||||
fun getTypeString(entry: DiskEntryContent) = when(entry.getTypeFlag()) {
|
||||
NORMAL_FILE -> "File"
|
||||
DIRECTORY -> "Directory"
|
||||
SYMLINK -> "Symbolic Link"
|
||||
else -> "(unknown type)"
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): AppendableByteBuffer {
|
||||
val serialisedContents = contents.serialize()
|
||||
val buffer = AppendableByteBuffer(HEADER_SIZE + serialisedContents.size)
|
||||
|
||||
buffer.put(entryID.toBigEndian())
|
||||
buffer.put(parentEntryID.toBigEndian())
|
||||
buffer.put(contents.getTypeFlag())
|
||||
buffer.put(filename.forceSize(NAME_LENGTH))
|
||||
buffer.put(creationDate.toInt48())
|
||||
buffer.put(modificationDate.toInt48())
|
||||
buffer.put(this.hashCode().toBigEndian())
|
||||
buffer.put(serialisedContents.array)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
override fun hashCode() = contents.serialize().getCRC32()
|
||||
|
||||
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
|
||||
|
||||
override fun toString() = "DiskEntry(name: ${getFilenameString(Charsets.UTF_8)}, index: $entryID, type: ${contents.getTypeFlag()}, crc: ${hashCode().toHex()})"
|
||||
}
|
||||
|
||||
|
||||
fun ByteArray.forceSize(size: Int): ByteArray {
|
||||
return ByteArray(size, { if (it < this.size) this[it] else 0.toByte() })
|
||||
}
|
||||
interface DiskEntryContent {
|
||||
fun serialize(): AppendableByteBuffer
|
||||
fun getSizePure(): Long
|
||||
fun getSizeEntry(): Long
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not retrieve bytes directly from this! Use VDUtil.retrieveFile(DiskEntry)
|
||||
* And besides, the bytes could be compressed.
|
||||
*/
|
||||
open class EntryFile(internal var bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : DiskEntryContent {
|
||||
|
||||
override fun getSizePure() = bytes.size
|
||||
override fun getSizeEntry() = getSizePure() + 6
|
||||
|
||||
/** Create new blank file */
|
||||
constructor(size: Long): this(net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size))
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(getSizeEntry())
|
||||
buffer.put(getSizePure().toInt48())
|
||||
buffer.put(bytes)
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
class EntryFileCompressed(internal var uncompressedSize: Long, bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : EntryFile(bytes) {
|
||||
|
||||
override fun getSizePure() = bytes.size
|
||||
override fun getSizeEntry() = getSizePure() + 12
|
||||
|
||||
/* No new blank file for the compressed */
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(getSizeEntry())
|
||||
buffer.put(getSizePure().toInt48())
|
||||
buffer.put(uncompressedSize.toInt48())
|
||||
buffer.put(bytes)
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
class EntryDirectory(private val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
|
||||
|
||||
override fun getSizePure() = entries.size * 4L
|
||||
override fun getSizeEntry() = getSizePure() + 2
|
||||
private fun checkCapacity(toAdd: Int = 1) {
|
||||
if (entries.size + toAdd > 65535)
|
||||
throw IOException("Directory entries limit exceeded.")
|
||||
}
|
||||
|
||||
fun add(entryID: EntryID) {
|
||||
checkCapacity()
|
||||
entries.add(entryID)
|
||||
}
|
||||
|
||||
fun remove(entryID: EntryID) {
|
||||
entries.remove(entryID)
|
||||
}
|
||||
|
||||
fun contains(entryID: EntryID) = entries.contains(entryID)
|
||||
|
||||
fun forEach(consumer: (EntryID) -> Unit) = entries.forEach(consumer)
|
||||
|
||||
val entryCount: Int
|
||||
get() = entries.size
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(getSizeEntry())
|
||||
buffer.put(entries.size.toShort().toBigEndian())
|
||||
entries.forEach { indexNumber -> buffer.put(indexNumber.toBigEndian()) }
|
||||
return buffer
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4L
|
||||
}
|
||||
}
|
||||
class EntrySymlink(val target: EntryID) : DiskEntryContent {
|
||||
|
||||
override fun getSizePure() = 4L
|
||||
override fun getSizeEntry() = 4L
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(4)
|
||||
return buffer.put(target.toBigEndian())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Int.toHex() = this.toLong().and(0xFFFFFFFF).toString(16).padStart(8, '0').toUpperCase()
|
||||
fun Int.toBigEndian(): ByteArray {
|
||||
return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() })
|
||||
}
|
||||
fun Long.toInt48(): ByteArray {
|
||||
return ByteArray(6, { this.ushr(40 - (8 * it)).toByte() })
|
||||
}
|
||||
fun Short.toBigEndian(): ByteArray {
|
||||
return byteArrayOf(
|
||||
this.div(256).toByte(),
|
||||
this.toByte()
|
||||
)
|
||||
}
|
||||
|
||||
fun AppendableByteBuffer.getCRC32(): Int {
|
||||
val crc = CRC32()
|
||||
this.array.forEachInt32 { crc.update(it) }
|
||||
return crc.value.toInt()
|
||||
}
|
||||
class AppendableByteBuffer(val size: Long) {
|
||||
val array = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size)
|
||||
private var offset = 0L
|
||||
|
||||
fun put(byteArray64: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64): AppendableByteBuffer {
|
||||
// it's slow but works
|
||||
// can't do system.arrayCopy directly
|
||||
byteArray64.forEach { put(it) }
|
||||
return this
|
||||
}
|
||||
fun put(byteArray: ByteArray): AppendableByteBuffer {
|
||||
byteArray.forEach { put(it) }
|
||||
return this
|
||||
}
|
||||
fun put(byte: Byte): AppendableByteBuffer {
|
||||
array[offset] = byte
|
||||
offset += 1
|
||||
return this
|
||||
}
|
||||
fun forEach(consumer: (Byte) -> Unit) = array.forEach(consumer)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,843 +0,0 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.finder
|
||||
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.nio.charset.Charset
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import javax.swing.*
|
||||
import javax.swing.table.AbstractTableModel
|
||||
import javax.swing.ListSelectionModel
|
||||
import javax.swing.text.DefaultCaret
|
||||
|
||||
|
||||
/**
|
||||
* Created by SKYHi14 on 2017-04-01.
|
||||
*/
|
||||
class VirtualDiskCracker(val sysCharset: Charset = Charsets.UTF_8) : JFrame() {
|
||||
|
||||
|
||||
private val annoyHackers = true // Jar build settings. Intended for Terrarum proj.
|
||||
|
||||
|
||||
private val PREVIEW_MAX_BYTES = 4L * 1024 // 4 kBytes
|
||||
|
||||
private val appName = "TerranVirtualDiskCracker"
|
||||
private val copyright = "Copyright 2017 Torvald (minjaesong). Distributed under MIT license."
|
||||
|
||||
private val magicOpen = "I solemnly swear that I am up to no good."
|
||||
private val magicSave = "Mischief managed."
|
||||
private val annoyWhenLaunchMsg = "Type in following to get started:\n$magicOpen"
|
||||
private val annoyWhenSaveMsg = "Type in following to save:\n$magicSave"
|
||||
|
||||
private val panelMain = JPanel()
|
||||
private val menuBar = JMenuBar()
|
||||
private val tableFiles: JTable
|
||||
private val fileDesc = JTextArea()
|
||||
private val diskInfo = JTextArea()
|
||||
private val statBar = JLabel("Open a disk or create new to get started")
|
||||
|
||||
private var vdisk: VirtualDisk? = null
|
||||
private var clipboard: DiskEntry? = null
|
||||
|
||||
private val labelPath = JLabel("(root)")
|
||||
private var currentDirectoryEntries: Array<DiskEntry>? = null
|
||||
private val directoryHierarchy = Stack<EntryID>(); init { directoryHierarchy.push(0) }
|
||||
|
||||
private fun gotoSubDirectory(id: EntryID) {
|
||||
directoryHierarchy.push(id)
|
||||
labelPath.text = vdisk!!.entries[id]!!.getFilenameString(sysCharset)
|
||||
selectedFile = null
|
||||
fileDesc.text = ""
|
||||
updateDiskInfo()
|
||||
}
|
||||
val currentDirectory: EntryID
|
||||
get() = directoryHierarchy.peek()
|
||||
val upperDirectory: EntryID
|
||||
get() = if (directoryHierarchy.lastIndex == 0) 0
|
||||
else directoryHierarchy[directoryHierarchy.lastIndex - 1]
|
||||
private fun gotoRoot() {
|
||||
directoryHierarchy.removeAllElements()
|
||||
directoryHierarchy.push(0)
|
||||
selectedFile = null
|
||||
fileDesc.text = ""
|
||||
updateDiskInfo()
|
||||
}
|
||||
private fun gotoParent() {
|
||||
if (directoryHierarchy.size > 1)
|
||||
directoryHierarchy.pop()
|
||||
selectedFile = null
|
||||
fileDesc.text = ""
|
||||
updateDiskInfo()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private var selectedFile: EntryID? = null
|
||||
|
||||
val tableColumns = arrayOf("Name", "Date Modified", "Size")
|
||||
val tableParentRecord = arrayOf(arrayOf("..", "", ""))
|
||||
|
||||
init {
|
||||
|
||||
if (annoyHackers) {
|
||||
val mantra = JOptionPane.showInputDialog(annoyWhenLaunchMsg)
|
||||
if (mantra != magicOpen) {
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
panelMain.layout = BorderLayout()
|
||||
this.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
||||
|
||||
|
||||
tableFiles = JTable(tableParentRecord, tableColumns)
|
||||
tableFiles.addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent) {
|
||||
val table = e.source as JTable
|
||||
val row = table.rowAtPoint(e.point)
|
||||
|
||||
|
||||
selectedFile = if (row > 0)
|
||||
currentDirectoryEntries!![row - 1].entryID
|
||||
else
|
||||
null // clicked ".."
|
||||
|
||||
|
||||
fileDesc.text = if (selectedFile != null) {
|
||||
getFileInfoText(vdisk!!.entries[selectedFile!!]!!)
|
||||
}
|
||||
else
|
||||
""
|
||||
|
||||
fileDesc.caretPosition = 0
|
||||
|
||||
// double click
|
||||
if (e.clickCount == 2) {
|
||||
if (row == 0) {
|
||||
gotoParent()
|
||||
}
|
||||
else {
|
||||
val record = currentDirectoryEntries!![row - 1]
|
||||
if (record.contents is EntryDirectory) {
|
||||
gotoSubDirectory(record.entryID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
tableFiles.selectionModel = object : DefaultListSelectionModel() {
|
||||
init { selectionMode = ListSelectionModel.SINGLE_SELECTION }
|
||||
override fun clearSelection() { } // required!
|
||||
override fun removeSelectionInterval(index0: Int, index1: Int) { } // required!
|
||||
override fun fireValueChanged(isAdjusting: Boolean) { } // required!
|
||||
}
|
||||
tableFiles.model = object : AbstractTableModel() {
|
||||
override fun getRowCount(): Int {
|
||||
return if (vdisk != null)
|
||||
1 + (currentDirectoryEntries?.size ?: 0)
|
||||
else 1
|
||||
}
|
||||
|
||||
override fun getColumnCount() = tableColumns.size
|
||||
|
||||
override fun getColumnName(column: Int) = tableColumns[column]
|
||||
|
||||
override fun getValueAt(rowIndex: Int, columnIndex: Int): Any {
|
||||
if (rowIndex == 0) {
|
||||
return tableParentRecord[0][columnIndex]
|
||||
}
|
||||
else {
|
||||
if (vdisk != null) {
|
||||
val entry = currentDirectoryEntries!![rowIndex - 1]
|
||||
return when(columnIndex) {
|
||||
0 -> entry.getFilenameString(sysCharset)
|
||||
1 -> Instant.ofEpochSecond(entry.modificationDate).
|
||||
atZone(TimeZone.getDefault().toZoneId()).
|
||||
format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
2 -> entry.getEffectiveSize()
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val menuFile = JMenu("File")
|
||||
menuFile.mnemonic = KeyEvent.VK_F
|
||||
menuFile.add("New Disk…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
try {
|
||||
val makeNewDisk: Boolean
|
||||
if (vdisk != null) {
|
||||
makeNewDisk = confirmedDiscard()
|
||||
}
|
||||
else {
|
||||
makeNewDisk = true
|
||||
}
|
||||
if (makeNewDisk) {
|
||||
// inquire new size
|
||||
val dialogBox = OptionDiskNameAndCap()
|
||||
val confirmNew = JOptionPane.OK_OPTION == dialogBox.showDialog("Set Property of New Disk")
|
||||
|
||||
if (confirmNew) {
|
||||
vdisk = VDUtil.createNewDisk(
|
||||
(dialogBox.capacity.value as Long).toLong(),
|
||||
dialogBox.name.text,
|
||||
sysCharset
|
||||
)
|
||||
gotoRoot()
|
||||
updateDiskInfo()
|
||||
setStat("Disk created")
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
})
|
||||
menuFile.add("Open Disk…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
val makeNewDisk: Boolean
|
||||
if (vdisk != null) {
|
||||
makeNewDisk = confirmedDiscard()
|
||||
}
|
||||
else {
|
||||
makeNewDisk = true
|
||||
}
|
||||
if (makeNewDisk) {
|
||||
val fileChooser = JFileChooser()
|
||||
fileChooser.showOpenDialog(null)
|
||||
if (fileChooser.selectedFile != null) {
|
||||
try {
|
||||
vdisk = VDUtil.readDiskArchive(fileChooser.selectedFile, Level.WARNING, { popupWarning(it) }, sysCharset)
|
||||
if (vdisk != null) {
|
||||
gotoRoot()
|
||||
updateDiskInfo()
|
||||
setStat("Disk loaded")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuFile.addSeparator()
|
||||
menuFile.add("Save Disk as…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
|
||||
|
||||
if (annoyHackers) {
|
||||
val mantra = JOptionPane.showInputDialog(annoyWhenSaveMsg)
|
||||
if (mantra != magicSave) {
|
||||
popupError("Nope!")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val fileChooser = JFileChooser()
|
||||
fileChooser.showSaveDialog(null)
|
||||
if (fileChooser.selectedFile != null) {
|
||||
try {
|
||||
VDUtil.dumpToRealMachine(vdisk!!, fileChooser.selectedFile)
|
||||
setStat("Disk saved")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuBar.add(menuFile)
|
||||
|
||||
val menuEdit = JMenu("Edit")
|
||||
menuEdit.mnemonic = KeyEvent.VK_E
|
||||
menuEdit.add("New File…").addMouseListener(object: MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val dialogBox = OptionFileNameAndCap()
|
||||
val confirmNew = JOptionPane.OK_OPTION == dialogBox.showDialog("Set Property of New File")
|
||||
|
||||
if (confirmNew) {
|
||||
if (VDUtil.nameExists(vdisk!!, dialogBox.name.text, currentDirectory, sysCharset)) {
|
||||
popupError("The name already exists")
|
||||
}
|
||||
else {
|
||||
VDUtil.createNewBlankFile(
|
||||
vdisk!!,
|
||||
currentDirectory,
|
||||
(dialogBox.capacity.value as Long).toLong(),
|
||||
dialogBox.name.text,
|
||||
sysCharset
|
||||
)
|
||||
updateDiskInfo()
|
||||
setStat("File created")
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("New Directory…").addMouseListener(object: MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
val newname = JOptionPane.showInputDialog("Enter a new directory name:")
|
||||
if (newname != null) {
|
||||
try {
|
||||
if (VDUtil.nameExists(vdisk!!, newname, currentDirectory, sysCharset)) {
|
||||
popupError("The name already exists")
|
||||
}
|
||||
else {
|
||||
VDUtil.addDir(vdisk!!, currentDirectory, newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
|
||||
updateDiskInfo()
|
||||
setStat("Directory created")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.addSeparator()
|
||||
menuEdit.add("Cut").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
// copy
|
||||
clipboard = vdisk!!.entries[selectedFile]
|
||||
|
||||
// delete
|
||||
if (vdisk != null && selectedFile != null) {
|
||||
try {
|
||||
VDUtil.deleteFile(vdisk!!, selectedFile!!)
|
||||
updateDiskInfo()
|
||||
setStat("File deleted")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Copy").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
clipboard = vdisk!!.entries[selectedFile]
|
||||
setStat("File copied")
|
||||
}
|
||||
})
|
||||
menuEdit.add("Paste").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
fun paste1(newname: ByteArray) {
|
||||
try {
|
||||
VDUtil.addFile(vdisk!!, currentDirectory, DiskEntry(// clone
|
||||
vdisk!!.generateUniqueID(),
|
||||
currentDirectory,
|
||||
newname,
|
||||
clipboard!!.creationDate,
|
||||
clipboard!!.modificationDate,
|
||||
clipboard!!.contents
|
||||
))
|
||||
|
||||
updateDiskInfo()
|
||||
setStat("File pasted")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
if (clipboard != null && vdisk != null) {
|
||||
// check name collision. If it is, ask for new one
|
||||
if (VDUtil.nameExists(vdisk!!, clipboard!!.getFilenameString(sysCharset), currentDirectory, sysCharset)) {
|
||||
val newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||
if (newname != null) {
|
||||
paste1(newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
|
||||
}
|
||||
}
|
||||
else {
|
||||
paste1(clipboard!!.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Paste as Symbolic Link").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
fun pasteSymbolic(newname: ByteArray) {
|
||||
try {
|
||||
// check if the original file is there in the first place
|
||||
if (vdisk!!.entries[clipboard!!.entryID] != null) {
|
||||
val entrySymlink = EntrySymlink(clipboard!!.entryID)
|
||||
VDUtil.addFile(vdisk!!, currentDirectory, DiskEntry(
|
||||
vdisk!!.generateUniqueID(),
|
||||
currentDirectory,
|
||||
newname,
|
||||
VDUtil.currentUnixtime,
|
||||
VDUtil.currentUnixtime,
|
||||
entrySymlink
|
||||
))
|
||||
|
||||
updateDiskInfo()
|
||||
setStat("Symbolic link created")
|
||||
}
|
||||
else {
|
||||
popupError("The orignal file is gone")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
if (clipboard != null && vdisk != null) {
|
||||
// check name collision. If it is, ask for new one
|
||||
if (VDUtil.nameExists(vdisk!!, clipboard!!.getFilenameString(sysCharset), currentDirectory, sysCharset)) {
|
||||
val newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||
if (newname != null) {
|
||||
pasteSymbolic(newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
|
||||
}
|
||||
}
|
||||
else {
|
||||
pasteSymbolic(clipboard!!.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Delete").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null && selectedFile != null) {
|
||||
try {
|
||||
VDUtil.deleteFile(vdisk!!, selectedFile!!)
|
||||
updateDiskInfo()
|
||||
setStat("File deleted")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Rename…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (selectedFile != null) {
|
||||
try {
|
||||
val newname = JOptionPane.showInputDialog("Enter a new name:")
|
||||
if (newname != null) {
|
||||
if (VDUtil.nameExists(vdisk!!, newname, currentDirectory, sysCharset)) {
|
||||
popupError("The name already exists")
|
||||
}
|
||||
else {
|
||||
VDUtil.renameFile(vdisk!!, selectedFile!!, newname, sysCharset)
|
||||
updateDiskInfo()
|
||||
setStat("File renamed")
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Look Clipboard").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
popupMessage(if (clipboard != null)
|
||||
"${clipboard ?: "(bug found)"}"
|
||||
else "(nothing)", "Clipboard"
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
menuEdit.addSeparator()
|
||||
menuEdit.add("Import…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
val fileChooser = JFileChooser()
|
||||
fileChooser.fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES
|
||||
fileChooser.isMultiSelectionEnabled = true
|
||||
fileChooser.showOpenDialog(null)
|
||||
if (fileChooser.selectedFiles.isNotEmpty()) {
|
||||
try {
|
||||
fileChooser.selectedFiles.forEach {
|
||||
if (!it.isDirectory) {
|
||||
val entry = VDUtil.importFile(it, vdisk!!.generateUniqueID(), sysCharset)
|
||||
|
||||
val newname: String?
|
||||
if (VDUtil.nameExists(vdisk!!, entry.filename, currentDirectory)) {
|
||||
newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||
}
|
||||
else {
|
||||
newname = entry.getFilenameString(sysCharset)
|
||||
}
|
||||
|
||||
if (newname != null) {
|
||||
entry.filename = newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset)
|
||||
VDUtil.addFile(vdisk!!, currentDirectory, entry)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val newname: String?
|
||||
if (VDUtil.nameExists(vdisk!!, it.name.toEntryName(DiskEntry.NAME_LENGTH, sysCharset), currentDirectory)) {
|
||||
newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||
}
|
||||
else {
|
||||
newname = it.name
|
||||
}
|
||||
|
||||
if (newname != null) {
|
||||
VDUtil.importDirRecurse(vdisk!!, it, currentDirectory, sysCharset, newname)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateDiskInfo()
|
||||
setStat("File added")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fileChooser.isMultiSelectionEnabled = false
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Export…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
val file = vdisk!!.entries[selectedFile ?: currentDirectory]!!
|
||||
|
||||
val fileChooser = JFileChooser()
|
||||
fileChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
|
||||
fileChooser.isMultiSelectionEnabled = false
|
||||
fileChooser.showSaveDialog(null)
|
||||
if (fileChooser.selectedFile != null) {
|
||||
try {
|
||||
val file = VDUtil.resolveIfSymlink(vdisk!!, file.entryID)
|
||||
if (file.contents is EntryFile) {
|
||||
VDUtil.exportFile(file.contents, fileChooser.selectedFile)
|
||||
setStat("File exported")
|
||||
}
|
||||
else {
|
||||
VDUtil.exportDirRecurse(vdisk!!, file.entryID, fileChooser.selectedFile, sysCharset)
|
||||
setStat("Files exported")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.addSeparator()
|
||||
menuEdit.add("Rename Disk…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val newname = JOptionPane.showInputDialog("Enter a new disk name:")
|
||||
if (newname != null) {
|
||||
vdisk!!.diskName = newname.toEntryName(VirtualDisk.NAME_LENGTH, sysCharset)
|
||||
updateDiskInfo()
|
||||
setStat("Disk renamed")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.add("Resize Disk…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val dialog = OptionSize()
|
||||
val confirmed = dialog.showDialog("Input") == JOptionPane.OK_OPTION
|
||||
if (confirmed) {
|
||||
vdisk!!.capacity = (dialog.capacity.value as Long).toLong()
|
||||
updateDiskInfo()
|
||||
setStat("Disk resized")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuEdit.addSeparator()
|
||||
menuEdit.add("Set/Unset Write Protection").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
vdisk!!.isReadOnly = vdisk!!.isReadOnly.not()
|
||||
updateDiskInfo()
|
||||
setStat("Disk write protection ${if (vdisk!!.isReadOnly) "" else "dis"}engaged")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuBar.add(menuEdit)
|
||||
|
||||
val menuManage = JMenu("Manage")
|
||||
menuManage.add("Report Orphans…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val reports = VDUtil.gcSearchOrphan(vdisk!!)
|
||||
val orphansCount = reports.size
|
||||
val orphansSize = reports.map { vdisk!!.entries[it]!!.contents.getSizeEntry() }.sum()
|
||||
val message = "Orphans count: $orphansCount\n" +
|
||||
"Size: ${orphansSize.bytes()}"
|
||||
popupMessage(message, "Orphans Report")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuManage.add("Report Phantoms…").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val reports = VDUtil.gcSearchPhantomBaby(vdisk!!)
|
||||
val phantomsSize = reports.size
|
||||
val message = "Phantoms count: $phantomsSize"
|
||||
popupMessage(message, "Phantoms Report")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuManage.addSeparator()
|
||||
menuManage.add("Remove Orphans").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val oldSize = vdisk!!.usedBytes
|
||||
VDUtil.gcDumpOrphans(vdisk!!)
|
||||
val newSize = vdisk!!.usedBytes
|
||||
popupMessage("Saved ${(oldSize - newSize).bytes()}", "GC Report")
|
||||
updateDiskInfo()
|
||||
setStat("Orphan nodes removed")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuManage.add("Full Garbage Collect").addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
if (vdisk != null) {
|
||||
try {
|
||||
val oldSize = vdisk!!.usedBytes
|
||||
VDUtil.gcDumpAll(vdisk!!)
|
||||
val newSize = vdisk!!.usedBytes
|
||||
popupMessage("Saved ${(oldSize - newSize).bytes()}", "GC Report")
|
||||
updateDiskInfo()
|
||||
setStat("Orphan nodes and null directory pointers removed")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
popupError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
menuBar.add(menuManage)
|
||||
|
||||
val menuAbout = JMenu("About")
|
||||
menuAbout.addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
popupMessage(copyright, "Copyright")
|
||||
}
|
||||
})
|
||||
menuBar.add(menuAbout)
|
||||
|
||||
|
||||
|
||||
diskInfo.highlighter = null
|
||||
diskInfo.text = "(Disk not loaded)"
|
||||
diskInfo.preferredSize = Dimension(-1, 60)
|
||||
|
||||
fileDesc.highlighter = null
|
||||
fileDesc.text = ""
|
||||
fileDesc.caret.isVisible = false
|
||||
(fileDesc.caret as DefaultCaret).updatePolicy = DefaultCaret.NEVER_UPDATE
|
||||
|
||||
val fileDescScroll = JScrollPane(fileDesc)
|
||||
val tableFilesScroll = JScrollPane(tableFiles)
|
||||
tableFilesScroll.size = Dimension(200, -1)
|
||||
|
||||
val panelFinder = JPanel(BorderLayout())
|
||||
panelFinder.add(labelPath, BorderLayout.NORTH)
|
||||
panelFinder.add(tableFilesScroll, BorderLayout.CENTER)
|
||||
|
||||
val panelFileDesc = JPanel(BorderLayout())
|
||||
panelFileDesc.add(JLabel("Entry Information"), BorderLayout.NORTH)
|
||||
panelFileDesc.add(fileDescScroll, BorderLayout.CENTER)
|
||||
|
||||
val filesSplit = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panelFinder, panelFileDesc)
|
||||
filesSplit.resizeWeight = 0.714285
|
||||
|
||||
|
||||
val panelDiskOp = JPanel(BorderLayout(2, 2))
|
||||
panelDiskOp.add(filesSplit, BorderLayout.CENTER)
|
||||
panelDiskOp.add(diskInfo, BorderLayout.SOUTH)
|
||||
|
||||
|
||||
panelMain.add(menuBar, BorderLayout.NORTH)
|
||||
panelMain.add(panelDiskOp, BorderLayout.CENTER)
|
||||
panelMain.add(statBar, BorderLayout.SOUTH)
|
||||
|
||||
|
||||
this.title = appName
|
||||
this.add(panelMain)
|
||||
this.setSize(700, 700)
|
||||
this.isVisible = true
|
||||
}
|
||||
|
||||
private fun confirmedDiscard() = 0 == JOptionPane.showOptionDialog(
|
||||
null, // parent
|
||||
"Any changes to current disk will be discarded. Continue?",
|
||||
"Confirm Discard", // window title
|
||||
JOptionPane.DEFAULT_OPTION, // option type
|
||||
JOptionPane.WARNING_MESSAGE, // message type
|
||||
null, // icon
|
||||
Popups.okCancel, // options (provided by JOptionPane.OK_CANCEL_OPTION in this case)
|
||||
Popups.okCancel[1] // default selection
|
||||
)
|
||||
private fun popupMessage(message: String, title: String = "") {
|
||||
JOptionPane.showOptionDialog(
|
||||
null,
|
||||
message,
|
||||
title,
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.INFORMATION_MESSAGE,
|
||||
null, null, null
|
||||
)
|
||||
}
|
||||
private fun popupError(message: String, title: String = "Uh oh…") {
|
||||
JOptionPane.showOptionDialog(
|
||||
null,
|
||||
message,
|
||||
title,
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE,
|
||||
null, null, null
|
||||
)
|
||||
}
|
||||
private fun popupWarning(message: String, title: String = "Careful…") {
|
||||
JOptionPane.showOptionDialog(
|
||||
null,
|
||||
message,
|
||||
title,
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE,
|
||||
null, null, null
|
||||
)
|
||||
}
|
||||
private fun updateCurrentDirectory() {
|
||||
currentDirectoryEntries = VDUtil.getDirectoryEntries(vdisk!!, currentDirectory)
|
||||
}
|
||||
private fun updateDiskInfo() {
|
||||
val sb = StringBuilder()
|
||||
directoryHierarchy.forEach {
|
||||
sb.append(vdisk!!.entries[it]!!.getFilenameString(sysCharset))
|
||||
sb.append('/')
|
||||
}
|
||||
sb.dropLast(1)
|
||||
labelPath.text = sb.toString()
|
||||
|
||||
diskInfo.text = if (vdisk == null) "(Disk not loaded)" else getDiskInfoText(vdisk!!)
|
||||
tableFiles.revalidate()
|
||||
tableFiles.repaint()
|
||||
|
||||
|
||||
updateCurrentDirectory()
|
||||
}
|
||||
private fun getDiskInfoText(disk: VirtualDisk): String {
|
||||
return """Name: ${String(disk.diskName, sysCharset)}
|
||||
Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free)
|
||||
Write protected: ${disk.isReadOnly.toEnglish()}"""
|
||||
}
|
||||
|
||||
|
||||
private fun Boolean.toEnglish() = if (this) "Yes" else "No"
|
||||
|
||||
|
||||
private fun getFileInfoText(file: DiskEntry): String {
|
||||
return """Name: ${file.getFilenameString(sysCharset)}
|
||||
Size: ${file.getEffectiveSize()}
|
||||
Type: ${DiskEntry.getTypeString(file.contents)}
|
||||
CRC: ${file.hashCode().toHex()}
|
||||
EntryID: ${file.entryID}
|
||||
ParentID: ${file.parentEntryID}""" + if (file.contents is EntryFile) """
|
||||
|
||||
Contents:
|
||||
${String(file.contents.bytes.sliceArray64(0L..minOf(PREVIEW_MAX_BYTES, file.contents.bytes.size) - 1).toByteArray(), sysCharset)}""" else ""
|
||||
}
|
||||
private fun Long.bytes() = if (this == 1L) "1 byte" else "$this bytes"
|
||||
private fun Int.entries() = if (this == 1) "1 entry" else "$this entries"
|
||||
private fun DiskEntry.getEffectiveSize() = if (this.contents is EntryFile)
|
||||
this.contents.getSizePure().bytes()
|
||||
else if (this.contents is EntryDirectory)
|
||||
this.contents.entryCount.entries()
|
||||
else if (this.contents is EntrySymlink)
|
||||
"(symlink)"
|
||||
else
|
||||
"n/a"
|
||||
private fun setStat(message: String) {
|
||||
statBar.text = message
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
VirtualDiskCracker(Charset.forName("CP437"))
|
||||
}
|
||||
Binary file not shown.
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||
import net.torvald.terrarum.modulebasegame.weather.WeatherMixer
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
|
||||
object WriteWorldInfo {
|
||||
|
||||
@@ -23,12 +22,11 @@ object WriteWorldInfo {
|
||||
/**
|
||||
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
|
||||
*
|
||||
* @return File on success; `null` on failure
|
||||
* @return List of ByteArray64, worldinfo0..worldinfo3; `null` on failure
|
||||
*/
|
||||
internal operator fun invoke(): List<File>? {
|
||||
val world = (Terrarum.ingame!!.world)
|
||||
internal operator fun invoke(world: GameWorld): List<ByteArray64>? {
|
||||
|
||||
val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
|
||||
//val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
|
||||
|
||||
val infileList = arrayOf(
|
||||
ModMgr.getGdxFilesFromEveryMod("blocks/blocks.csv"),
|
||||
@@ -36,40 +34,34 @@ object WriteWorldInfo {
|
||||
ModMgr.getGdxFilesFromEveryMod("materials/materials.csv")
|
||||
)
|
||||
|
||||
val metaFile = File(path + "0")
|
||||
|
||||
val outFiles = ArrayList<File>()
|
||||
outFiles.add(metaFile)
|
||||
|
||||
val outFiles = ArrayList<ByteArray64>() // for worldinfo1-3 only
|
||||
val worldInfoHash = ArrayList<ByteArray>() // hash of worldinfo1-3
|
||||
// try to write worldinfo1-3
|
||||
|
||||
for (filenum in 1..HASHED_FILES_COUNT) {
|
||||
val outFile = File(path + filenum.toString())
|
||||
if (outFile.exists()) outFile.delete()
|
||||
outFile.createNewFile()
|
||||
|
||||
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 256)
|
||||
val outputStream = ByteArray64GrowableOutputStream()
|
||||
val infile = infileList[filenum - 1]
|
||||
|
||||
infile.forEach {
|
||||
outputStream.write("## from file: ${it.nameWithoutExtension()} ##############################\n".toByteArray())
|
||||
val readBytes = it.readBytes()
|
||||
outputStream.write(readBytes)
|
||||
outputStream.write("\n".toByteArray())
|
||||
}
|
||||
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
|
||||
outFiles.add(outFile)
|
||||
outFiles.add(outputStream.toByteArray64())
|
||||
|
||||
|
||||
worldInfoHash.add(DigestUtils.sha256(FileInputStream(outFile)))
|
||||
worldInfoHash.add(DigestUtils.sha256(ByteArray64InputStream(outputStream.toByteArray64())))
|
||||
}
|
||||
|
||||
|
||||
// compose save meta (actual writing part)
|
||||
val metaOut = BufferedOutputStream(FileOutputStream(metaFile), 256)
|
||||
val metaOut = ByteArray64GrowableOutputStream()
|
||||
|
||||
|
||||
metaOut.write(META_MAGIC)
|
||||
@@ -78,8 +70,11 @@ object WriteWorldInfo {
|
||||
|
||||
// world name
|
||||
val worldNameBytes = world.worldName.toByteArray(Charsets.UTF_8)
|
||||
metaOut.write(worldNameBytes)
|
||||
if (worldNameBytes.last() != NULL) metaOut.write(NULL.toInt())
|
||||
//metaOut.write(worldNameBytes)
|
||||
worldNameBytes.forEach {
|
||||
if (it != 0.toByte()) metaOut.write(it.toInt())
|
||||
}
|
||||
metaOut.write(NULL.toInt())
|
||||
|
||||
// terrain seed
|
||||
metaOut.write(world.generatorSeed.toLittle())
|
||||
@@ -125,7 +120,7 @@ object WriteWorldInfo {
|
||||
|
||||
|
||||
|
||||
return outFiles.toList()
|
||||
return listOf(metaOut.toByteArray64()) + outFiles.toList()
|
||||
}
|
||||
|
||||
}
|
||||
69
src/net/torvald/terrarum/tests/GsonTest.kt
Normal file
69
src/net/torvald/terrarum/tests/GsonTest.kt
Normal file
@@ -0,0 +1,69 @@
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.google.gson.GsonBuilder
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import org.dyn4j.geometry.Vector2
|
||||
|
||||
/**
|
||||
* My anecdotes: GSON does NOT like anonymous class!
|
||||
*
|
||||
* Created by minjaesong on 2019-02-22
|
||||
*/
|
||||
object GsonTest {
|
||||
|
||||
operator fun invoke() {
|
||||
val jsonString = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
.toJson(GsonTestActor())
|
||||
|
||||
|
||||
println(jsonString)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GsonTestActor : Actor(Actor.RenderOrder.MIDDLE) {
|
||||
override fun update(delta: Float) {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override fun onActorValueChange(key: String, value: Any?) {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
init {
|
||||
referenceID = 42
|
||||
}
|
||||
|
||||
private val mysecretnote = "note"
|
||||
}
|
||||
|
||||
class GsonTestClass : GsonTestInterface {
|
||||
var foo = 1.567f
|
||||
val bar = "stingy"
|
||||
val baz = Color(0f, 0.2f, 0.4f, 1f)
|
||||
val fov = Vector2(1.324324, -0.4321)
|
||||
|
||||
val bazget: Color
|
||||
get() = Color.CHARTREUSE
|
||||
|
||||
@Transient override var superfoo = 42
|
||||
@Transient val tbar = "i'm invisible"
|
||||
}
|
||||
|
||||
interface GsonTestInterface {
|
||||
var superfoo: Int
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
GsonTest.invoke()
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user