gson test in progress

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
package net.torvald.terrarum.serialise
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import net.torvald.terrarum.realestate.LandUtil
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream
@@ -48,14 +46,16 @@ internal object WriteLayerDataZip {
/**
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
* @param world The world to serialise
* @param path The directory where the temporary file goes, in relative to the AppLoader.defaultSaveDir. Should NOT start with slashed
*
* @return File on success; `null` on failure
*/
internal operator fun invoke(): File? {
val world = (Terrarum.ingame!!.world)
internal operator fun invoke(world: GameWorld): ByteArray64? {
//val sanitisedPath = path.replace('\\', '/').removePrefix("/").replace("../", "")
val path = "${AppLoader.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
//val path = "${AppLoader.defaultSaveDir}/$sanitisedPath/tmp_$LAYERS_FILENAME${world.worldIndex}"
//val path = "${AppLoader.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
// TODO let's try dump-on-the-disk-then-pack method...
@@ -69,11 +69,12 @@ internal object WriteLayerDataZip {
}*/
val outFile = File(path)
if (outFile.exists()) outFile.delete()
outFile.createNewFile()
//val outFile = File(path)
//if (outFile.exists()) outFile.delete()
//outFile.createNewFile()
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 8192)
//val outputStream = BufferedOutputStream(FileOutputStream(outFile), 8192)
val outputStream = ByteArray64GrowableOutputStream()
var deflater: DeflaterOutputStream // couldn't really use one outputstream for all the files.
fun wb(byteArray: ByteArray) { outputStream.write(byteArray) }
@@ -196,15 +197,12 @@ internal object WriteLayerDataZip {
// END OF WRITE //
//////////////////
// replace savemeta with tempfile
try {
outputStream.flush()
outputStream.close()
return outFile
return outputStream.toByteArray64()
}
catch (e: IOException) {
e.printStackTrace()

View File

@@ -1,16 +1,15 @@
package net.torvald.terrarum.serialise
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
import net.torvald.terrarum.modulebasegame.weather.WeatherMixer
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream
import org.apache.commons.codec.digest.DigestUtils
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
object WriteWorldInfo {
@@ -23,12 +22,11 @@ object WriteWorldInfo {
/**
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
*
* @return File on success; `null` on failure
* @return List of ByteArray64, worldinfo0..worldinfo3; `null` on failure
*/
internal operator fun invoke(): List<File>? {
val world = (Terrarum.ingame!!.world)
internal operator fun invoke(world: GameWorld): List<ByteArray64>? {
val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
//val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo"
val infileList = arrayOf(
ModMgr.getGdxFilesFromEveryMod("blocks/blocks.csv"),
@@ -36,40 +34,34 @@ object WriteWorldInfo {
ModMgr.getGdxFilesFromEveryMod("materials/materials.csv")
)
val metaFile = File(path + "0")
val outFiles = ArrayList<File>()
outFiles.add(metaFile)
val outFiles = ArrayList<ByteArray64>() // for worldinfo1-3 only
val worldInfoHash = ArrayList<ByteArray>() // hash of worldinfo1-3
// try to write worldinfo1-3
for (filenum in 1..HASHED_FILES_COUNT) {
val outFile = File(path + filenum.toString())
if (outFile.exists()) outFile.delete()
outFile.createNewFile()
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 256)
val outputStream = ByteArray64GrowableOutputStream()
val infile = infileList[filenum - 1]
infile.forEach {
outputStream.write("## from file: ${it.nameWithoutExtension()} ##############################\n".toByteArray())
val readBytes = it.readBytes()
outputStream.write(readBytes)
outputStream.write("\n".toByteArray())
}
outputStream.flush()
outputStream.close()
outFiles.add(outFile)
outFiles.add(outputStream.toByteArray64())
worldInfoHash.add(DigestUtils.sha256(FileInputStream(outFile)))
worldInfoHash.add(DigestUtils.sha256(ByteArray64InputStream(outputStream.toByteArray64())))
}
// compose save meta (actual writing part)
val metaOut = BufferedOutputStream(FileOutputStream(metaFile), 256)
val metaOut = ByteArray64GrowableOutputStream()
metaOut.write(META_MAGIC)
@@ -78,8 +70,11 @@ object WriteWorldInfo {
// world name
val worldNameBytes = world.worldName.toByteArray(Charsets.UTF_8)
metaOut.write(worldNameBytes)
if (worldNameBytes.last() != NULL) metaOut.write(NULL.toInt())
//metaOut.write(worldNameBytes)
worldNameBytes.forEach {
if (it != 0.toByte()) metaOut.write(it.toInt())
}
metaOut.write(NULL.toInt())
// terrain seed
metaOut.write(world.generatorSeed.toLittle())
@@ -125,7 +120,7 @@ object WriteWorldInfo {
return outFiles.toList()
return listOf(metaOut.toByteArray64()) + outFiles.toList()
}
}

View File

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

View File

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

View File

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

View File

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