diff --git a/src/net/torvald/terrarum/StateInGame.kt b/src/net/torvald/terrarum/StateInGame.kt index bbb8ab7c6..16c9e97fc 100644 --- a/src/net/torvald/terrarum/StateInGame.kt +++ b/src/net/torvald/terrarum/StateInGame.kt @@ -96,14 +96,14 @@ class StateInGame : BasicGameState() { // UI aliases (no pause) val uiAliases = HashMap() - private val UI_PIE_MENU = "uiPieMenu" - private val UI_QUICK_BAR = "uiQuickBar" - private val UI_INVENTORY_PLAYER = "uiInventoryPlayer" - private val UI_INVENTORY_CONTAINER = "uiInventoryContainer" - private val UI_VITAL1 = "uiVital1" - private val UI_VITAL2 = "uiVital2" - private val UI_VITAL3 = "uiVital3" - private val UI_CONSOLE = "uiConsole" + val UI_PIE_MENU = "uiPieMenu" + val UI_QUICK_BAR = "uiQuickBar" + val UI_INVENTORY_PLAYER = "uiInventoryPlayer" + val UI_INVENTORY_CONTAINER = "uiInventoryContainer" + val UI_VITAL_PRIMARY = "uiVital1" + val UI_VITAL_SECONDARY = "uiVital2" + val UI_VITAL_ITEM = "uiVital3" // itemcount/durability of held block or active ammo of held gun. As for the block, max value is 500. + val UI_CONSOLE = "uiConsole" // UI aliases (pause) val uiAlasesPausing = HashMap() @@ -204,12 +204,12 @@ class StateInGame : BasicGameState() { // vital metre // fill in getter functions by // (uiAliases[UI_QUICK_BAR]!!.UI as UIVitalMetre).vitalGetterMax = { some_function } - uiAliases[UI_VITAL1] = UIHandler(UIVitalMetre(player, { 80f }, { 100f }, Color.red, 0), customPositioning = true) - uiAliases[UI_VITAL1]!!.setAsAlwaysVisible() - uiAliases[UI_VITAL2] = UIHandler(UIVitalMetre(player, { 73f }, { 100f }, Color(0x00dfff), 1), customPositioning = true) - uiAliases[UI_VITAL2]!!.setAsAlwaysVisible() - uiAliases[UI_VITAL3] = UIHandler(UIVitalMetre(player, { 32f }, { 100f }, Color(0xffcc00), 2), customPositioning = true) - uiAliases[UI_VITAL3]!!.setAsAlwaysVisible() + uiAliases[UI_VITAL_PRIMARY] = UIHandler(UIVitalMetre(player, { 80f }, { 100f }, Color.red, 0), customPositioning = true) + uiAliases[UI_VITAL_PRIMARY]!!.setAsAlwaysVisible() + uiAliases[UI_VITAL_SECONDARY] = UIHandler(UIVitalMetre(player, { 73f }, { 100f }, Color(0x00dfff), 1), customPositioning = true) + uiAliases[UI_VITAL_SECONDARY]!!.setAsAlwaysVisible() + uiAliases[UI_VITAL_ITEM] = UIHandler(UIVitalMetre(player, { 32f }, { 100f }, Color(0xffcc00), 2), customPositioning = true) + uiAliases[UI_VITAL_ITEM]!!.setAsAlwaysVisible() // batch-process uiAliases @@ -673,7 +673,7 @@ class StateInGame : BasicGameState() { if (it is Pocketed) { it.inventory.forEach { inventoryEntry -> inventoryEntry.item.effectWhileInPocket(gc, delta) - if (it.isEquipped(inventoryEntry.item)) { + if (it.equipped(inventoryEntry.item)) { inventoryEntry.item.effectWhenEquipped(gc, delta) } } diff --git a/src/net/torvald/terrarum/UIItemInventoryElem.kt b/src/net/torvald/terrarum/UIItemInventoryElem.kt index e0d50409f..7b29d992e 100644 --- a/src/net/torvald/terrarum/UIItemInventoryElem.kt +++ b/src/net/torvald/terrarum/UIItemInventoryElem.kt @@ -27,6 +27,7 @@ class UIItemInventoryElem( val mouseOverTextCol: Color = Color(0xfff066), val mouseoverBackCol: Color = Color(0,0,0,0), val mouseoverBackBlendMode: String = BlendMode.NORMAL, + val inactiveTextCol: Color = UIItemTextButton.defaultInactiveCol, val backCol: Color = Color(0,0,0,0), val backBlendMode: String = BlendMode.NORMAL, var quickslot: Int? = null, @@ -88,7 +89,7 @@ class UIItemInventoryElem( // if mouse is over, text lights up // this one-liner sets color - g.color = item!!.nameColour mul if (mouseUp) mouseOverTextCol else UIItemTextButton.defaultInactiveCol + g.color = item!!.nameColour mul if (mouseUp) mouseOverTextCol else inactiveTextCol g.drawString( item!!.name + (if (amount > 0 && !item!!.isUnique) "${0x3000.toChar()}($amount)" else "") + (if (equippedSlot != null) " ${0xE081.toChar()}\$$equippedSlot" else ""), diff --git a/src/net/torvald/terrarum/console/Inventory.kt b/src/net/torvald/terrarum/console/Inventory.kt index 41c043c43..86d53dc21 100644 --- a/src/net/torvald/terrarum/console/Inventory.kt +++ b/src/net/torvald/terrarum/console/Inventory.kt @@ -57,20 +57,14 @@ internal object Inventory : ConsoleCommand { private fun addItem(refId: Int, amount: Int = 1) { if (target != null) { - target!!.inventory.add(ItemCodex[refId], amount) + target!!.addItem(ItemCodex[refId], amount) } } private fun equipItem(refId: Int) { if (target != null) { val item = ItemCodex[refId] - - // if the item does not exist, add it first - if (!target!!.inventory.hasItem(item)) { - target!!.inventory.add(item) - } - - target!!.inventory.itemEquipped[item.equipPosition] = item + target!!.equipItem(item) } } diff --git a/src/net/torvald/terrarum/gameactors/ActorInventory.kt b/src/net/torvald/terrarum/gameactors/ActorInventory.kt index cd59e77ae..df70a005c 100644 --- a/src/net/torvald/terrarum/gameactors/ActorInventory.kt +++ b/src/net/torvald/terrarum/gameactors/ActorInventory.kt @@ -29,6 +29,7 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode * Sorted by referenceID. */ private val itemList = ArrayList() + init { } @@ -135,8 +136,8 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode - fun hasItem(item: InventoryItem) = hasItem(item.id) - fun hasItem(id: Int) = + fun contains(item: InventoryItem) = contains(item.id) + fun contains(id: Int) = if (itemList.size == 0) false else diff --git a/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt b/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt index cfea0ebf8..e2c34f99f 100644 --- a/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt +++ b/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt @@ -9,6 +9,7 @@ import net.torvald.terrarum.gameactors.ActorHumanoid import net.torvald.terrarum.gameactors.faction.FactionFactory import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.mapdrawer.FeaturesDrawer +import net.torvald.terrarum.tileproperties.Tile import net.torvald.terrarum.to10bit import net.torvald.terrarum.toInt import org.newdawn.slick.Color @@ -75,8 +76,15 @@ object PlayerBuilderSigrid { // Test fill up inventory - p.inventory.add(16, 512) - p.equipItem(ItemCodex[16]) + val tiles = arrayOf( + Tile.AIR, Tile.DIRT, Tile.GLASS_CRUDE, + Tile.GRASS, Tile.GRAVEL, Tile.ICE_MAGICAL, Tile.LANTERN, + Tile.PLANK_BIRCH, Tile.PLANK_BLOODROSE, Tile.PLANK_EBONY, Tile.PLANK_NORMAL, + Tile.SANDSTONE, Tile.SANDSTONE_BLACK, Tile.SANDSTONE_GREEN, + Tile.SANDSTONE_RED, Tile.STONE, Tile.STONE_BRICKS, + Tile.STONE_QUARRIED, Tile.STONE_TILE_WHITE, Tile.TORCH + ) + tiles.forEach { p.inventory.add(it, 999) } diff --git a/src/net/torvald/terrarum/gameactors/Pocketed.kt b/src/net/torvald/terrarum/gameactors/Pocketed.kt index 79108b642..0d59517da 100644 --- a/src/net/torvald/terrarum/gameactors/Pocketed.kt +++ b/src/net/torvald/terrarum/gameactors/Pocketed.kt @@ -2,6 +2,7 @@ package net.torvald.terrarum.gameactors import net.torvald.terrarum.Terrarum import net.torvald.terrarum.gameitem.InventoryItem +import net.torvald.terrarum.itemproperties.ItemCodex import java.util.* /** @@ -18,7 +19,7 @@ interface Pocketed { if (item.equipPosition == InventoryItem.EquipPosition.NULL) throw Error("Unequipping the item that cannot be equipped") - if (!inventory.hasItem(item)) + if (!inventory.contains(item)) throw Error("Unequipping the item that does not exist in inventory") inventory.itemEquipped[item.equipPosition] = null @@ -29,7 +30,7 @@ interface Pocketed { * Equips an item. If the item is not in the inventory, adds the item first. */ fun equipItem(item: InventoryItem) { - if (!inventory.hasItem(item)) + if (!inventory.contains(item)) inventory.add(item) if (item.equipPosition >= 0) { @@ -38,10 +39,17 @@ interface Pocketed { } } - fun isEquipped(item: InventoryItem): Boolean { + fun equipped(item: InventoryItem): Boolean { return inventory.itemEquipped[item.equipPosition] == item } + fun addItem(itemID: Int, count: Int = 1) = inventory.add(ItemCodex[itemID], count) + fun addItem(item: InventoryItem, count: Int = 1) = inventory.add(item, count) + fun removeItem(itemID: Int, count: Int = 1) = inventory.remove(ItemCodex[itemID], count) + fun removeItem(item: InventoryItem, count: Int = 1) = inventory.remove(item, count) + + fun hasItem(item: InventoryItem) = inventory.contains(item.id) + fun hasItem(id: Int) = inventory.contains(id) fun consumePrimary(item: InventoryItem) { diff --git a/src/net/torvald/terrarum/gameactors/ThreadActorUpdate.kt b/src/net/torvald/terrarum/gameactors/ThreadActorUpdate.kt index d2383b873..d3d64a62d 100644 --- a/src/net/torvald/terrarum/gameactors/ThreadActorUpdate.kt +++ b/src/net/torvald/terrarum/gameactors/ThreadActorUpdate.kt @@ -16,7 +16,7 @@ class ThreadActorUpdate(val startIndex: Int, val endIndex: Int, if (it is Pocketed) { it.inventory.forEach { inventoryEntry -> inventoryEntry.item.effectWhileInPocket(gc, delta) - if (it.isEquipped(inventoryEntry.item)) { + if (it.equipped(inventoryEntry.item)) { inventoryEntry.item.effectWhenEquipped(gc, delta) } } diff --git a/src/net/torvald/terrarum/gameitem/DynamicItem.kt b/src/net/torvald/terrarum/gameitem/DynamicItem.kt index 9f10897b7..d18427d5f 100644 --- a/src/net/torvald/terrarum/gameitem/DynamicItem.kt +++ b/src/net/torvald/terrarum/gameitem/DynamicItem.kt @@ -29,7 +29,7 @@ open abstract class DynamicItem(val baseItemID: Int?, newMass: Double? = null, n var ret: Int do { ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID - } while (ItemCodex.hasItem(ret) || ret < ItemCodex.ITEM_DYNAMIC_MIN || ret > ItemCodex.ITEM_DYNAMIC_MAX) // check for collision + } while (ItemCodex.contains(ret) || ret < ItemCodex.ITEM_DYNAMIC_MIN || ret > ItemCodex.ITEM_DYNAMIC_MAX) // check for collision return ret } diff --git a/src/net/torvald/terrarum/ui/UIInventory.kt b/src/net/torvald/terrarum/ui/UIInventory.kt index cc9937b9e..df1280c14 100644 --- a/src/net/torvald/terrarum/ui/UIInventory.kt +++ b/src/net/torvald/terrarum/ui/UIInventory.kt @@ -33,6 +33,7 @@ class UIInventory( val catButtonsToCatIdent = HashMap() val backgroundColour = Color(0x80242424.toInt()) + val defaultTextColour = Color(0xeaeaea) init { catButtonsToCatIdent.put("GAME_INVENTORY_WEAPONS", InventoryItem.Category.WEAPON) @@ -79,11 +80,13 @@ class UIInventory( defaultSelection = 0, iconSpriteSheet = SpriteSheet("./assets/graphics/gui/inventory/category.tga", 20, 20), iconSpriteSheetIndices = intArrayOf(9,6,7,0,1,2,3,4,5,8), + iconCol = defaultTextColour, highlightBackCol = Color(0xb8b8b8), highlightBackBlendMode = BlendMode.MULTIPLY, backgroundCol = Color(0,0,0,0), // will use custom background colour! backgroundBlendMode = BlendMode.NORMAL, - kinematic = true + kinematic = true, + inactiveCol = defaultTextColour ) val itemsStripWidth = ((width - catButtons.width) - (2 * itemStripGutterH + itemInterColGutter)) / 2 @@ -101,7 +104,8 @@ class UIInventory( mouseoverBackBlendMode = BlendMode.SCREEN, backCol = Color(0xd4d4d4), backBlendMode = BlendMode.MULTIPLY, - drawBackOnNull = true + drawBackOnNull = true, + inactiveTextCol = defaultTextColour ) }) val itemsScrollOffset = 0 @@ -234,7 +238,7 @@ class UIInventory( // texts blendNormal() - g.color = Color(0xe8e8e8) + g.color = defaultTextColour // W - close g.drawString(listControlClose, 4f, height - controlHelpHeight.toFloat()) // MouseL - Use ; 1.9 - Register ; T - Drop diff --git a/src/net/torvald/terrarum/ui/UIItemTextButtonList.kt b/src/net/torvald/terrarum/ui/UIItemTextButtonList.kt index 93098d4ae..e5832787a 100644 --- a/src/net/torvald/terrarum/ui/UIItemTextButtonList.kt +++ b/src/net/torvald/terrarum/ui/UIItemTextButtonList.kt @@ -26,18 +26,19 @@ class UIItemTextButtonList( val textAreaWidth: Int, val iconSpriteSheet: SpriteSheet? = null, val iconSpriteSheetIndices: IntArray? = null, + val iconCol: Color = UIItemTextButton.defaultInactiveCol, // copied directly from UIItemTextButton val activeCol: Color = Color(0xfff066), - val activeBackCol: Color = Color(0,0,0,0), + val activeBackCol: Color = Color(0, 0, 0, 0), val activeBackBlendMode: String = BlendMode.NORMAL, val highlightCol: Color = Color(0x00f8ff), val highlightBackCol: Color = Color(0xb0b0b0), val highlightBackBlendMode: String = BlendMode.MULTIPLY, val inactiveCol: Color = Color(0xc0c0c0), - val backgroundCol: Color = Color(0,0,0,0), + val backgroundCol: Color = Color(0, 0, 0, 0), val backgroundBlendMode: String = BlendMode.NORMAL, - val kinematic: Boolean = false // more "kinetic" movement of selector + val kinematic: Boolean = false ) : UIItem(parentUI) { val iconToTextGap = 20 @@ -158,7 +159,8 @@ class UIItemTextButtonList( iconSpriteSheetIndices!!.forEachIndexed { counter, imageIndex -> iconSpriteSheet.getSubImage(imageIndex, 0).draw( 32f, - buttons[counter].posY + iconY.toFloat() + buttons[counter].posY + iconY.toFloat(), + iconCol ) } } diff --git a/src/net/torvald/terrarum/ui/UIVitalMetre.kt b/src/net/torvald/terrarum/ui/UIVitalMetre.kt index 7927e99dd..c629a4c91 100644 --- a/src/net/torvald/terrarum/ui/UIVitalMetre.kt +++ b/src/net/torvald/terrarum/ui/UIVitalMetre.kt @@ -23,6 +23,11 @@ class UIVitalMetre( val order: Int ) : UICanvas { + init { + // semitransparent + color?.a = 0.91f + } + private val margin = 25 private val gap = 4f @@ -45,7 +50,12 @@ class UIVitalMetre( private val theta = 33f private val halfTheta = theta / 2f - private val backColor: Color; get() = color?.darkerLab(0.4f) ?: Color.black + private val backColor: Color + get(): Color { + val c = (color?.darkerLab(0.33f) ?: Color.black) + c.a = 0.7f + return c + } override fun update(gc: GameContainer, delta: Int) { @@ -66,9 +76,11 @@ class UIVitalMetre( ) - g.lineWidth = 2f + + val ratio = minOf(1f, vitalGetterVal()!! / vitalGetterMax()!!) + // background g.color = backColor g.drawArc( @@ -77,7 +89,7 @@ class UIVitalMetre( circleRadius * 2f + order * gap * 2, circleRadius * 2f + order * gap * 2, 90f - halfTheta, - 90f + halfTheta - theta * (vitalGetterVal()!! / vitalGetterMax()!!) + 90f + halfTheta - theta * ratio ) g.color = color @@ -86,7 +98,7 @@ class UIVitalMetre( -circleRadius - order * gap - offsetY, circleRadius * 2f + order * gap * 2, circleRadius * 2f + order * gap * 2, - 90f + halfTheta - theta * (vitalGetterVal()!! / vitalGetterMax()!!), + 90f + halfTheta - theta * ratio, 90f + halfTheta ) diff --git a/src/net/torvald/terrarum/virtualcomputer/luaapi/FilesystemTEVD.kt b/src/net/torvald/terrarum/virtualcomputer/luaapi/FilesystemTEVD.kt index 6ba61f20e..da17e7f97 100644 --- a/src/net/torvald/terrarum/virtualcomputer/luaapi/FilesystemTEVD.kt +++ b/src/net/torvald/terrarum/virtualcomputer/luaapi/FilesystemTEVD.kt @@ -199,7 +199,7 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) { val file = computer.getFile(path) try { if (file!!.contents is EntryFile) - return LuaValue.valueOf(file.contents.getSizePure()) + return LuaValue.valueOf(file.contents.getSizePure().toInt()) else if (file.contents is EntryDirectory) return LuaValue.valueOf(file.contents.entries.size) } @@ -364,7 +364,7 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) { when (mode) { "r" -> { try { - val fr = StringReader(String(file.bytes, sysCharset))//FileReader(file) + val fr = StringReader(String(file.bytes.toByteArray(), sysCharset))//FileReader(file) luaClass["close"] = FileClassClose(fr) luaClass["readLine"] = FileClassReadLine(fr) luaClass["readAll"] = FileClassReadAll(file) @@ -381,7 +381,7 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) { } "rb" -> { try { - val fis = ByteArrayInputStream(file.bytes) + val fis = ByteArrayInputStream(file.bytes.toByteArray()) luaClass["close"] = FileClassClose(fis) luaClass["read"] = FileClassReadByte(fis) luaClass["readAll"] = FileClassReadAll(file) @@ -492,13 +492,13 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) { private class FileClassReadAllBytes(val file: EntryFile) : ZeroArgFunction() { override fun call() : LuaValue { - return LuaValue.valueOf(String(file.bytes, sysCharset)) + return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset)) } } private class FileClassReadAll(val file: EntryFile) : ZeroArgFunction() { override fun call() : LuaValue { - return LuaValue.valueOf(String(file.bytes, sysCharset)) + return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset)) } } diff --git a/src/net/torvald/terrarum/virtualcomputer/peripheral/PeripheralVideoCard.kt b/src/net/torvald/terrarum/virtualcomputer/peripheral/PeripheralVideoCard.kt index b0c34738f..c501fb308 100644 --- a/src/net/torvald/terrarum/virtualcomputer/peripheral/PeripheralVideoCard.kt +++ b/src/net/torvald/terrarum/virtualcomputer/peripheral/PeripheralVideoCard.kt @@ -43,7 +43,7 @@ class PeripheralVideoCard(val host: TerrarumComputer, val termW: Int = 80, val t val width = termW * blockW val height = termH * blockH - val spritesCount = 64 + val spritesCount = 256 val vram = VRAM(width, height, spritesCount) val frameBuffer = ImageBuffer(width, height) diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt new file mode 100644 index 000000000..eef296a31 --- /dev/null +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt @@ -0,0 +1,128 @@ +package net.torvald.terrarum.virtualcomputer.tvd + +import java.io.File +import java.io.FileOutputStream + + +/** + * ByteArray that can hold larger than 4 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) { + private val bankSize: Int = 1 shl 30 // 2^30 Bytes, or 1 GiB + private val data: Array + + init { + if (size <= 0) + throw IllegalArgumentException("Invalid array size!") + + val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt() + + data = Array( + requiredBanks, + { bankIndex -> + kotlin.ByteArray( + if (bankIndex == requiredBanks - 1) + size.toBankOffset() + else + bankSize, + + { 0.toByte() } + ) + } + ) + } + + private fun Long.toBankNumber(): Int = (this / bankSize).toInt() + private fun Long.toBankOffset(): Int = (this % bankSize).toInt() + + operator fun set(index: Long, value: Byte) { + if (index < 0 || index >= size) + throw ArrayIndexOutOfBoundsException("size $size, index $index") + + data[index.toBankNumber()][index.toBankOffset()] = value + } + + operator fun get(index: Long): Byte { + if (index < 0 || index >= size) + throw ArrayIndexOutOfBoundsException("size $size, index $index") + + return data[index.toBankNumber()][index.toBankOffset()] + } + + operator fun iterator(): ByteIterator { + return object : ByteIterator() { + var iterationCounter = 0L + + override fun nextByte(): Byte { + iterationCounter += 1 + return this@ByteArray64[iterationCounter - 1] + } + + override fun hasNext() = iterationCounter < this@ByteArray64.size + } + } + + fun iteratorChoppedToInt(): IntIterator { + return object : IntIterator() { + var iterationCounter = 0L + val iteratorSize = 1 + ((this@ByteArray64.size - 1) / 4).toInt() + + override fun nextInt(): Int { + var byteCounter = iterationCounter * 4L + var int = 0 + (0..3).forEach { + if (byteCounter + it < this@ByteArray64.size) { + int += this@ByteArray64[byteCounter + it].toInt() shl (it * 8) + } + else { + int += 0 shl (it * 8) + } + } + + + iterationCounter += 1 + return int + } + + override fun hasNext() = iterationCounter < iteratorSize + } + } + + fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) } + fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) } + + fun sliceArray(range: LongRange): ByteArray64 { + val newarr = ByteArray64(range.last - range.first + 1) + range.forEach { index -> + newarr[index - range.first] = this[index] + } + 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() + } + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt index 273bf7ac8..84c42fc2c 100644 --- a/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt @@ -81,9 +81,29 @@ object VDUtil { } } + fun File.writeBytes64(array: ByteArray64) { + array.writeToFile(this) + } + + fun File.readBytes64(): ByteArray64 { + val inbytes = ByteArray64(this.length()) + val inputStream = BufferedInputStream(FileInputStream(this)) + var readInt = inputStream.read() + var readInCounter = 0L + while (readInt != -1) { + inbytes[readInCounter] = readInt.toByte() + readInCounter += 1 + + readInt = inputStream.read() + } + inputStream.close() + + return inbytes + } + fun dumpToRealMachine(disk: VirtualDisk, outfile: File) { if (!outfile.exists()) outfile.createNewFile() - outfile.writeBytes(disk.serialize().array) + outfile.writeBytes64(disk.serialize().array) } /** @@ -92,52 +112,60 @@ object VDUtil { * @param crcWarnLevel Level.OFF -- no warning, Level.WARNING -- print out warning, Level.SEVERE -- throw error */ fun readDiskArchive(infile: File, crcWarnLevel: Level = Level.SEVERE, warningFunc: ((String) -> Unit)? = null, charset: Charset): VirtualDisk { - val inbytes = infile.readBytes() + val inbytes = infile.readBytes64() - if (magicMismatch(VirtualDisk.MAGIC, inbytes)) + + + if (magicMismatch(VirtualDisk.MAGIC, inbytes.sliceArray(0L..3L).toByteArray())) throw RuntimeException("Invalid Virtual Disk file!") - val diskSize = inbytes.sliceArray(4..7).toIntBig() - val diskName = inbytes.sliceArray(8..8 + 31) - val diskCRC = inbytes.sliceArray(8 + 32..8 + 32 + 3).toIntBig() // to check with completed vdisk + val diskSize = inbytes.sliceArray(4L..9L).toInt48Big() + val diskName = inbytes.sliceArray(10L..10L + 31) + val diskCRC = inbytes.sliceArray(10L + 32..10L + 32 + 3).toIntBig() // to check with completed vdisk + val diskSpecVersion = inbytes[10L + 32 + 4] - val vdisk = VirtualDisk(diskSize, diskName) + + if (diskSpecVersion != specversion) + throw RuntimeException("Unsupported disk format version: current internal version is $specversion; the file's version is $diskSpecVersion") + + val vdisk = VirtualDisk(diskSize, diskName.toByteArray()) //println("[VDUtil] currentUnixtime = $currentUnixtime") - var entryOffset = 44 - while (!Arrays.equals(inbytes.sliceArray(entryOffset..entryOffset + 3), VirtualDisk.FOOTER_START_MARK)) { + var entryOffset = VirtualDisk.HEADER_SIZE + while (!Arrays.equals(inbytes.sliceArray(entryOffset..entryOffset + 3).toByteArray(), VirtualDisk.FOOTER_START_MARK)) { //println("[VDUtil] entryOffset = $entryOffset") // read and prepare all the shits - val entryIndexNum = inbytes.sliceArray(entryOffset..entryOffset + 3).toIntBig() - val entryTypeFlag = inbytes[entryOffset + 4] - val entryFileName = inbytes.sliceArray(entryOffset + 5..entryOffset + 260) - val entryCreationTime = inbytes.sliceArray(entryOffset + 261..entryOffset + 268).toLongBig() - val entryModifyTime = inbytes.sliceArray(entryOffset + 269..entryOffset + 276).toLongBig() + val entryID = inbytes.sliceArray(entryOffset..entryOffset + 3).toIntBig() + val entryParentID = inbytes.sliceArray(entryOffset + 4..entryOffset + 7).toIntBig() + val entryTypeFlag = inbytes[entryOffset + 8] + val entryFileName = inbytes.sliceArray(entryOffset + 9..entryOffset + 9 + 255).toByteArray() + val entryCreationTime = inbytes.sliceArray(entryOffset + 265..entryOffset + 270).toInt48Big() + val entryModifyTime = inbytes.sliceArray(entryOffset + 271..entryOffset + 276).toInt48Big() val entryCRC = inbytes.sliceArray(entryOffset + 277..entryOffset + 280).toIntBig() // to check with completed entry val entryData = when (entryTypeFlag) { DiskEntry.NORMAL_FILE -> { - val filesize = inbytes.sliceArray(entryOffset + 281..entryOffset + 284).toIntBig() + val filesize = inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 5).toInt48Big() //println("[VDUtil] --> is file; filesize = $filesize") - inbytes.sliceArray(entryOffset + 285..entryOffset + 284 + filesize) + inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE + 6..entryOffset + DiskEntry.HEADER_SIZE + 5 + filesize) } DiskEntry.DIRECTORY -> { - val entryCount = inbytes.sliceArray(entryOffset + 281..entryOffset + 282).toShortBig() + val entryCount = inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 1).toShortBig() //println("[VDUtil] --> is directory; entryCount = $entryCount") - inbytes.sliceArray(entryOffset + 283..entryOffset + 282 + entryCount * 4) + inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE + 2..entryOffset + DiskEntry.HEADER_SIZE + 1 + entryCount * 4) } DiskEntry.SYMLINK -> { - inbytes.sliceArray(entryOffset + 281..entryOffset + 284) + inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 3) } - else -> throw RuntimeException("Unknown entry with type $entryTypeFlag") + else -> throw RuntimeException("Unknown entry with type $entryTypeFlag at entryOffset $entryOffset") } // update entryOffset so that we can fetch next entry in the binary - entryOffset += 281 + entryData.size + when (entryTypeFlag) { - DiskEntry.NORMAL_FILE -> 4 + entryOffset += DiskEntry.HEADER_SIZE + entryData.size + when (entryTypeFlag) { + DiskEntry.NORMAL_FILE -> 6 DiskEntry.DIRECTORY -> 2 DiskEntry.SYMLINK -> 0 else -> throw RuntimeException("Unknown entry with type $entryTypeFlag") @@ -146,7 +174,8 @@ object VDUtil { // create entry val diskEntry = DiskEntry( - entryID = entryIndexNum, + entryID = entryID, + parentEntryID = entryParentID, filename = entryFileName, creationDate = entryCreationTime, modificationDate = entryModifyTime, @@ -173,7 +202,7 @@ object VDUtil { val calculatedCRC = diskEntry.hashCode() val crcMsg = "CRC failed: expected ${entryCRC.toHex()}, got ${calculatedCRC.toHex()}\n" + - "at file \"${diskEntry.getFilenameString(charset)}\" (entry ID ${diskEntry.entryID})" + "at file \"${diskEntry.getFilenameString(charset)}\" (entry ID ${diskEntry.entryID})" if (calculatedCRC != entryCRC) { if (crcWarnLevel == Level.SEVERE) @@ -184,7 +213,7 @@ object VDUtil { } // add entry to disk - vdisk.entries[entryIndexNum] = diskEntry + vdisk.entries[entryID] = diskEntry } @@ -294,12 +323,12 @@ object VDUtil { */ private fun DiskEntry.getAsNormalFile(disk: VirtualDisk): EntryFile = this.contents as? EntryFile ?: - if (this.contents is EntryDirectory) - throw RuntimeException("this is directory") - else if (this.contents is EntrySymlink) - disk.entries[this.contents.target]!!.getAsNormalFile(disk) - else - throw RuntimeException("Unknown entry type") + if (this.contents is EntryDirectory) + throw RuntimeException("this is directory") + else if (this.contents is EntrySymlink) + disk.entries[this.contents.target]!!.getAsNormalFile(disk) + else + throw RuntimeException("Unknown entry type") /** * SYNOPSIS disk.getFile("bin/msh.lua")!!.first.getAsNormalFile(disk) * @@ -307,12 +336,12 @@ object VDUtil { */ private fun DiskEntry.getAsDirectory(disk: VirtualDisk): EntryDirectory = this.contents as? EntryDirectory ?: - if (this.contents is EntrySymlink) - disk.entries[this.contents.target]!!.getAsDirectory(disk) - else if (this.contents is EntryFile) - throw RuntimeException("this is not directory") - else - throw RuntimeException("Unknown entry type") + if (this.contents is EntrySymlink) + disk.entries[this.contents.target]!!.getAsDirectory(disk) + else if (this.contents is EntryFile) + throw RuntimeException("this is not directory") + else + throw RuntimeException("Unknown entry type") /** * Search for the file and returns a instance of normal file. @@ -342,20 +371,26 @@ object VDUtil { val fileSearchResult = getFile(disk, path)!! - return deleteFile(disk, fileSearchResult.parent.entryID, fileSearchResult.file.entryID) + return deleteFile(disk, fileSearchResult.file.entryID) } /** * Deletes file on the disk safely. */ - fun deleteFile(disk: VirtualDisk, parentID: EntryID, targetID: EntryID) { + fun deleteFile(disk: VirtualDisk, targetID: EntryID) { disk.checkReadOnly() val file = disk.entries[targetID] + + if (file == null) { + throw FileNotFoundException("No such file to delete") + } + + val parentID = file.parentEntryID val parentDir = disk.entries[parentID] fun rollback() { if (!disk.entries.contains(targetID)) { - disk.entries[targetID] = file!! + disk.entries[targetID] = file } if (!(parentDir!!.contents as EntryDirectory).entries.contains(targetID)) { (parentDir.contents as EntryDirectory).entries.add(targetID) @@ -365,14 +400,16 @@ object VDUtil { if (parentDir == null || parentDir.contents !is EntryDirectory) { throw FileNotFoundException("No such parent directory") } - else if (file == null || !directoryContains(disk, parentID, targetID)) { + // check if directory "parentID" has "targetID" in the first place + else if (!directoryContains(disk, parentID, targetID)) { throw FileNotFoundException("No such file to delete") } else if (targetID == 0) { throw IOException("Cannot delete root file system") } else if (file.contents is EntryDirectory && file.contents.entries.size > 0) { - throw IOException("Cannot delete directory that contains something") + //throw IOException("Cannot delete directory that contains something") + deleteDirRecurse(disk, targetID) } else { try { @@ -414,24 +451,26 @@ object VDUtil { } } /** - * Add file to the specified directory. + * Add file to the specified directory. ParentID of the file will be overwritten. */ fun addFile(disk: VirtualDisk, parentPath: VDPath, file: DiskEntry) { disk.checkReadOnly() disk.checkCapacity(file.serialisedSize) try { + val parentID = getFile(disk, parentPath)!!.file.entryID // add record to the directory getAsDirectory(disk, parentPath).entries.add(file.entryID) // add entry on the disk disk.entries[file.entryID] = file + file.parentEntryID = parentID } catch (e: KotlinNullPointerException) { throw FileNotFoundException("No such directory") } } /** - * Add file to the specified directory. + * Add file to the specified directory. ParentID of the file will be overwritten. */ fun addFile(disk: VirtualDisk, directoryID: EntryID, file: DiskEntry) { disk.checkReadOnly() @@ -442,6 +481,7 @@ object VDUtil { getAsDirectory(disk, directoryID).entries.add(file.entryID) // add entry on the disk disk.entries[file.entryID] = file + file.parentEntryID = directoryID } catch (e: KotlinNullPointerException) { throw FileNotFoundException("No such directory") @@ -457,11 +497,13 @@ object VDUtil { val newID = disk.generateUniqueID() try { + val parentID = getFile(disk, parentPath)!!.file.entryID // add record to the directory getAsDirectory(disk, parentPath).entries.add(newID) // add entry on the disk disk.entries[newID] = DiskEntry( newID, + parentID, name, currentUnixtime, currentUnixtime, @@ -487,6 +529,7 @@ object VDUtil { // add entry on the disk disk.entries[newID] = DiskEntry( newID, + directoryID, name, currentUnixtime, currentUnixtime, @@ -498,6 +541,45 @@ object VDUtil { } } + fun deleteDirRecurse(disk: VirtualDisk, directoryID: EntryID) { + val entriesToDelete = ArrayList() + + fun recurse1(entry: DiskEntry?) { + // return conditions + if (entry == null) return + if (entry.contents !is EntryDirectory) { + entriesToDelete.add(entry.entryID) + return + } + // recurse + else { + entry.contents.entries.forEach { + entriesToDelete.add(entry.entryID) + recurse1(disk.entries[it]) + } + } + } + + val entry = disk.entries[directoryID] + if (entry != null && entry.contents is EntryDirectory) { + entry.contents.entries.forEach { + entriesToDelete.add(directoryID) + recurse1(disk.entries[it]) + } + + // delete entries + entriesToDelete.forEach { disk.entries.remove(it) } + // GC + gcDumpAll(disk) + System.gc() + } + else if (entry == null) { + throw FileNotFoundException("No such directory") + } + else { + throw IOException("The file is not a directory") + } + } /** * Imports external file and returns corresponding DiskEntry. @@ -509,10 +591,11 @@ object VDUtil { return DiskEntry( entryID = id, + parentEntryID = 0, // placeholder filename = file.name.toByteArray(), creationDate = currentUnixtime, modificationDate = currentUnixtime, - contents = EntryFile(file.readBytes()) + contents = EntryFile(file.readBytes64()) ) } /** @@ -520,7 +603,7 @@ object VDUtil { */ fun exportFile(entryFile: EntryFile, outfile: File) { outfile.createNewFile() - outfile.writeBytes(entryFile.bytes) + outfile.writeBytes64(entryFile.bytes) } /** @@ -557,7 +640,7 @@ object VDUtil { try { deleteFile(disk2, toPath) } - catch (e: KotlinNullPointerException) { "Nothing to delete beforehand" } + catch (e: KotlinNullPointerException) { /* Nothing to delete beforehand */ } deleteFile(disk1, fromPath) // any uncaught no_from_file will be caught here try { @@ -578,10 +661,11 @@ object VDUtil { /** * Creates new disk with given name and capacity */ - fun createNewDisk(diskSize: Int, diskName: String, charset: Charset): VirtualDisk { + fun createNewDisk(diskSize: Long, diskName: String, charset: Charset): VirtualDisk { val newdisk = VirtualDisk(diskSize, diskName.toEntryName(VirtualDisk.NAME_LENGTH, charset)) val rootDir = DiskEntry( entryID = 0, + parentEntryID = 0, filename = DiskEntry.ROOTNAME.toByteArray(charset), creationDate = currentUnixtime, modificationDate = currentUnixtime, @@ -595,12 +679,13 @@ object VDUtil { /** * Creates new zero-filled file with given name and size */ - fun createNewBlankFile(disk: VirtualDisk, directoryID: EntryID, fileSize: Int, filename: String, charset: Charset) { + fun createNewBlankFile(disk: VirtualDisk, directoryID: EntryID, fileSize: Long, filename: String, charset: Charset) { disk.checkReadOnly() disk.checkCapacity(fileSize + DiskEntry.HEADER_SIZE + 4) addFile(disk, directoryID, DiskEntry( disk.generateUniqueID(), + directoryID, filename.toEntryName(DiskEntry.NAME_LENGTH, charset = charset), currentUnixtime, currentUnixtime, @@ -619,29 +704,31 @@ object VDUtil { /** * Throws an exception if specified size cannot fit into the disk */ - fun VirtualDisk.checkCapacity(newSize: Int) { + fun VirtualDisk.checkCapacity(newSize: Long) { if (this.usedBytes + newSize > this.capacity) throw IOException("Not enough space on the disk") } - fun ByteArray.toIntBig(): Int { - if (this.size != 4) + fun ByteArray64.toIntBig(): Int { + if (this.size != 4L) throw OperationNotSupportedException("ByteArray is not Int") var i = 0 - this.forEachIndexed { index, byte -> i += byte.toUint().shl(24 - index * 8)} + var c = 0 + this.forEach { byte -> i += byte.toUint().shl(24 - c * 8); c += 1 } return i } - fun ByteArray.toLongBig(): Long { - if (this.size != 8) + fun ByteArray64.toInt48Big(): Long { + if (this.size != 6L) throw OperationNotSupportedException("ByteArray is not Long") var i = 0L - this.forEachIndexed { index, byte -> i += byte.toUint().shl(56 - index * 8)} + var c = 0 + this.forEach { byte -> i += byte.toUint().shl(40 - c * 8); c += 1 } return i } - fun ByteArray.toShortBig(): Short { - if (this.size != 2) - throw OperationNotSupportedException("ByteArray is not Long") + fun ByteArray64.toShortBig(): Short { + if (this.size != 2L) + throw OperationNotSupportedException("ByteArray is not Short") return (this[0].toUint().shl(256) + this[1].toUint()).toShort() } @@ -685,17 +772,66 @@ object VDUtil { return dir.contents.entries.contains(targetID) } } + + /** + * Searches for disconnected nodes using its parent pointer. + * If the parent node is invalid, the node is considered orphan, and will be added + * to the list this function returns. + * + * @return List of orphan entries + */ + fun gcSearchOrphan(disk: VirtualDisk): List { + return disk.entries.filter { disk.entries[it.value.parentEntryID] == null }.keys.toList() + } + + fun gcSearchPhantomBaby(disk: VirtualDisk): List> { + // Pair + val phantoms = ArrayList>() + disk.entries.filter { it.value.contents is EntryDirectory }.values.forEach { directory -> + (directory.contents as EntryDirectory).entries.forEach { dirEntryID -> + if (disk.entries[dirEntryID] == null) { + phantoms.add(Pair(directory.entryID, dirEntryID)) + } + } + } + return phantoms + } + + fun gcDumpOrphans(disk: VirtualDisk) { + try { + gcSearchOrphan(disk).forEach { + disk.entries.remove(it) + } + } + catch (e: Exception) { + throw InternalError("Aw, snap! Here's what it says:\n$e") + } + } + + fun gcDumpAll(disk: VirtualDisk) { + try { + gcSearchPhantomBaby(disk).forEach { + getAsDirectory(disk, it.first).entries.remove(it.second) + } + gcSearchOrphan(disk).forEach { + disk.entries.remove(it) + } + } + catch (e: Exception) { + throw InternalError("Aw, snap! Here's what it says:\n$e") + } + } } fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this) fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean { - return !Arrays.equals(array.sliceArray(0..magic.lastIndex), magic) + return !Arrays.equals(array, magic) } fun String.toEntryName(length: Int, charset: Charset): ByteArray { - val buffer = AppendableByteBuffer(length) + val buffer = AppendableByteBuffer(length.toLong()) val stringByteArray = this.toByteArray(charset) buffer.put(stringByteArray.sliceArray(0..minOf(length, stringByteArray.size) - 1)) - return buffer.array + return buffer.array.toByteArray() } fun ByteArray.toCanonicalString(charset: Charset): String { var lastIndexOfRealStr = 0 @@ -708,9 +844,26 @@ fun ByteArray.toCanonicalString(charset: Charset): String { return String(this.sliceArray(0..lastIndexOfRealStr), charset) } +fun ArrayList.toByteArray64(): ByteArray64 { + val array = ByteArray64(this.size.toLong()) + this.forEachIndexed { index, byte -> + array[index.toLong()] = byte + } + return array +} +fun ByteArray.toByteArray64(): ByteArray64 { + val array = ByteArray64(this.size.toLong()) + this.forEachIndexed { index, byte -> + array[index.toLong()] = byte + } + return array +} + /** * Writes String to the file * + * Note: this FileWriter cannot write more than 2 GiB + * * @param fileEntry must be File, resolve symlink beforehand * @param mode "w" or "a" */ @@ -728,8 +881,8 @@ class VDFileWriter(private val fileEntry: DiskEntry, private val append: Boolean override fun write(cbuf: CharArray, off: Int, len: Int) { if (!closed) { - val newByteArray = String(cbuf).toByteArray(charset) - newFileBuffer.addAll(newByteArray.asIterable()) + val newByteArray = String(cbuf).toByteArray(charset).toByteArray64() + newByteArray.forEach { newFileBuffer.add(it) } } else { throw IOException() @@ -741,16 +894,16 @@ class VDFileWriter(private val fileEntry: DiskEntry, private val append: Boolean val newByteArray = newFileBuffer.toByteArray() if (!append) { - (fileEntry.contents as EntryFile).bytes = newByteArray + (fileEntry.contents as EntryFile).bytes = newByteArray.toByteArray64() } else { - val oldByteArray = (fileEntry.contents as EntryFile).bytes.copyOf() + val oldByteArray = (fileEntry.contents as EntryFile).bytes.toByteArray().copyOf() val newFileBuffer = ByteArray(oldByteArray.size + newByteArray.size) System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size) System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size) - (fileEntry.contents as EntryFile).bytes = newByteArray + fileEntry.contents.bytes = newByteArray.toByteArray64() } newFileBuffer = ArrayList() @@ -788,16 +941,16 @@ class VDFileOutputStream(private val fileEntry: DiskEntry, private val append: B val newByteArray = newFileBuffer.toByteArray() if (!append) { - (fileEntry.contents as EntryFile).bytes = newByteArray + (fileEntry.contents as EntryFile).bytes = newByteArray.toByteArray64() } else { - val oldByteArray = (fileEntry.contents as EntryFile).bytes.copyOf() + val oldByteArray = (fileEntry.contents as EntryFile).bytes.toByteArray().copyOf() val newFileBuffer = ByteArray(oldByteArray.size + newByteArray.size) System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size) System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size) - (fileEntry.contents as EntryFile).bytes = newByteArray + fileEntry.contents.bytes = newByteArray.toByteArray64() } newFileBuffer = ArrayList() diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt index 4e1ac2dd6..f062d23f5 100644 --- a/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt @@ -11,27 +11,31 @@ import java.util.zip.CRC32 typealias EntryID = Int +val specversion = 0x02.toByte() + class VirtualDisk( /** capacity of 0 makes the disk read-only */ - var capacity: Int, + var capacity: Long, var diskName: ByteArray = ByteArray(NAME_LENGTH) ) { val entries = HashMap() val isReadOnly: Boolean - get() = capacity == 0 + get() = capacity == 0L fun getDiskNameString(charset: Charset) = String(diskName, charset) val root: DiskEntry get() = entries[0]!! - private fun serializeEntriesOnly(): ByteArray { + private fun serializeEntriesOnly(): ByteArray64 { val bufferList = ArrayList() entries.forEach { val serialised = it.value.serialize() serialised.forEach { bufferList.add(it) } } - return ByteArray(bufferList.size, { bufferList[it] }) + val byteArray = ByteArray64(bufferList.size.toLong()) + bufferList.forEachIndexed { index, byte -> byteArray[index.toLong()] = byte } + return byteArray } fun serialize(): AppendableByteBuffer { @@ -40,9 +44,10 @@ class VirtualDisk( val crc = hashCode().toBigEndian() buffer.put(MAGIC) - buffer.put(capacity.toBigEndian()) + 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(EOF_MARK) @@ -53,7 +58,7 @@ class VirtualDisk( override fun hashCode(): Int { val crcList = IntArray(entries.size) var crcListAppendCursor = 0 - entries.forEach { t, u -> + entries.forEach { _, u -> crcList[crcListAppendCursor] = u.hashCode() crcListAppendCursor++ } @@ -65,7 +70,7 @@ class VirtualDisk( } /** Expected size of the virtual disk */ - val usedBytes: Int + val usedBytes: Long get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE fun generateUniqueID(): Int { @@ -80,8 +85,8 @@ class VirtualDisk( override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})" companion object { - val HEADER_SIZE = 44 // according to the spec - val FOOTER_SIZE = 6 // footer mark + EOF + val HEADER_SIZE = 47L // according to the spec + val FOOTER_SIZE = 6L // footer mark + EOF val NAME_LENGTH = 32 val MAGIC = "TEVd".toByteArray() @@ -95,6 +100,7 @@ class VirtualDisk( class DiskEntry( // header var entryID: EntryID, + var parentEntryID: EntryID, var filename: ByteArray = ByteArray(NAME_LENGTH), var creationDate: Long, var modificationDate: Long, @@ -104,11 +110,11 @@ class DiskEntry( ) { fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset) - val serialisedSize: Int + val serialisedSize: Long get() = contents.getSizeEntry() + HEADER_SIZE companion object { - val HEADER_SIZE = 281 // according to the spec + val HEADER_SIZE = 281L // according to the spec val ROOTNAME = "(root)" val NAME_LENGTH = 256 @@ -131,13 +137,14 @@ class DiskEntry( fun serialize(): AppendableByteBuffer { val serialisedContents = contents.serialize() - val buffer = AppendableByteBuffer(281 + serialisedContents.size) + 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.toBigEndian()) - buffer.put(modificationDate.toBigEndian()) + buffer.put(creationDate.toInt48()) + buffer.put(modificationDate.toInt48()) buffer.put(this.hashCode().toBigEndian()) buffer.put(serialisedContents.array) @@ -157,27 +164,27 @@ fun ByteArray.forceSize(size: Int): ByteArray { } interface DiskEntryContent { fun serialize(): AppendableByteBuffer - fun getSizePure(): Int - fun getSizeEntry(): Int + fun getSizePure(): Long + fun getSizeEntry(): Long } -class EntryFile(var bytes: ByteArray) : DiskEntryContent { +class EntryFile(var bytes: ByteArray64) : DiskEntryContent { override fun getSizePure() = bytes.size - override fun getSizeEntry() = getSizePure() + 4 + override fun getSizeEntry() = getSizePure() + 6 /** Create new blank file */ - constructor(size: Int): this(ByteArray(size)) + constructor(size: Long): this(ByteArray64(size)) override fun serialize(): AppendableByteBuffer { val buffer = AppendableByteBuffer(getSizeEntry()) - buffer.put(getSizePure().toBigEndian()) + buffer.put(getSizePure().toInt48()) buffer.put(bytes) return buffer } } class EntryDirectory(val entries: ArrayList = ArrayList()) : DiskEntryContent { - override fun getSizePure() = entries.size * 4 + override fun getSizePure() = entries.size * 4L override fun getSizeEntry() = getSizePure() + 2 override fun serialize(): AppendableByteBuffer { @@ -188,13 +195,13 @@ class EntryDirectory(val entries: ArrayList = ArrayList()) : D } companion object { - val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4 + val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4L } } class EntrySymlink(val target: EntryID) : DiskEntryContent { - override fun getSizePure() = 4 - override fun getSizeEntry() = 4 + override fun getSizePure() = 4L + override fun getSizeEntry() = 4L override fun serialize(): AppendableByteBuffer { val buffer = AppendableByteBuffer(4) @@ -207,9 +214,8 @@ fun Int.toHex() = this.toLong().and(0xFFFFFFFF).toString(16).padStart(8, '0').to fun Int.toBigEndian(): ByteArray { return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() }) } -fun Long.toBigEndian(): ByteArray { - return ByteArray(8, { this.ushr(56 - (8 * it)).toByte() }) - +fun Long.toInt48(): ByteArray { + return ByteArray(6, { this.ushr(40 - (8 * it)).toByte() }) } fun Short.toBigEndian(): ByteArray { return byteArrayOf( @@ -217,24 +223,24 @@ fun Short.toBigEndian(): ByteArray { this.toByte() ) } + fun AppendableByteBuffer.getCRC32(): Int { val crc = CRC32() - crc.update(this.array) + this.array.forEachInt32 { crc.update(it) } return crc.value.toInt() } -class AppendableByteBuffer(val size: Int) { - val array = ByteArray(size, { 0.toByte() }) - private var offset = 0 +class AppendableByteBuffer(val size: Long) { + val array = ByteArray64(size) + private var offset = 0L + fun put(byteArray64: ByteArray64): AppendableByteBuffer { + // it's slow but works + // can't do system.arrayCopy directly + byteArray64.forEach { put(it) } + return this + } fun put(byteArray: ByteArray): AppendableByteBuffer { - System.arraycopy( - byteArray, // source - 0, // source pos - array, // destination - offset, // destination pos - byteArray.size // length - ) - offset += byteArray.size + byteArray.forEach { put(it) } return this } fun put(byte: Byte): AppendableByteBuffer {