inventory ui update

This commit is contained in:
Song Minjae
2017-04-13 03:25:49 +09:00
parent 47b9b92797
commit a47eb41d9a
16 changed files with 476 additions and 159 deletions

View File

@@ -96,14 +96,14 @@ class StateInGame : BasicGameState() {
// UI aliases (no pause) // UI aliases (no pause)
val uiAliases = HashMap<String, UIHandler>() val uiAliases = HashMap<String, UIHandler>()
private val UI_PIE_MENU = "uiPieMenu" val UI_PIE_MENU = "uiPieMenu"
private val UI_QUICK_BAR = "uiQuickBar" val UI_QUICK_BAR = "uiQuickBar"
private val UI_INVENTORY_PLAYER = "uiInventoryPlayer" val UI_INVENTORY_PLAYER = "uiInventoryPlayer"
private val UI_INVENTORY_CONTAINER = "uiInventoryContainer" val UI_INVENTORY_CONTAINER = "uiInventoryContainer"
private val UI_VITAL1 = "uiVital1" val UI_VITAL_PRIMARY = "uiVital1"
private val UI_VITAL2 = "uiVital2" val UI_VITAL_SECONDARY = "uiVital2"
private val UI_VITAL3 = "uiVital3" val UI_VITAL_ITEM = "uiVital3" // itemcount/durability of held block or active ammo of held gun. As for the block, max value is 500.
private val UI_CONSOLE = "uiConsole" val UI_CONSOLE = "uiConsole"
// UI aliases (pause) // UI aliases (pause)
val uiAlasesPausing = HashMap<String, UIHandler>() val uiAlasesPausing = HashMap<String, UIHandler>()
@@ -204,12 +204,12 @@ class StateInGame : BasicGameState() {
// vital metre // vital metre
// fill in getter functions by // fill in getter functions by
// (uiAliases[UI_QUICK_BAR]!!.UI as UIVitalMetre).vitalGetterMax = { some_function } // (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_VITAL_PRIMARY] = UIHandler(UIVitalMetre(player, { 80f }, { 100f }, Color.red, 0), customPositioning = true)
uiAliases[UI_VITAL1]!!.setAsAlwaysVisible() uiAliases[UI_VITAL_PRIMARY]!!.setAsAlwaysVisible()
uiAliases[UI_VITAL2] = UIHandler(UIVitalMetre(player, { 73f }, { 100f }, Color(0x00dfff), 1), customPositioning = true) uiAliases[UI_VITAL_SECONDARY] = UIHandler(UIVitalMetre(player, { 73f }, { 100f }, Color(0x00dfff), 1), customPositioning = true)
uiAliases[UI_VITAL2]!!.setAsAlwaysVisible() uiAliases[UI_VITAL_SECONDARY]!!.setAsAlwaysVisible()
uiAliases[UI_VITAL3] = UIHandler(UIVitalMetre(player, { 32f }, { 100f }, Color(0xffcc00), 2), customPositioning = true) uiAliases[UI_VITAL_ITEM] = UIHandler(UIVitalMetre(player, { 32f }, { 100f }, Color(0xffcc00), 2), customPositioning = true)
uiAliases[UI_VITAL3]!!.setAsAlwaysVisible() uiAliases[UI_VITAL_ITEM]!!.setAsAlwaysVisible()
// batch-process uiAliases // batch-process uiAliases
@@ -673,7 +673,7 @@ class StateInGame : BasicGameState() {
if (it is Pocketed) { if (it is Pocketed) {
it.inventory.forEach { inventoryEntry -> it.inventory.forEach { inventoryEntry ->
inventoryEntry.item.effectWhileInPocket(gc, delta) inventoryEntry.item.effectWhileInPocket(gc, delta)
if (it.isEquipped(inventoryEntry.item)) { if (it.equipped(inventoryEntry.item)) {
inventoryEntry.item.effectWhenEquipped(gc, delta) inventoryEntry.item.effectWhenEquipped(gc, delta)
} }
} }

View File

@@ -27,6 +27,7 @@ class UIItemInventoryElem(
val mouseOverTextCol: Color = Color(0xfff066), val mouseOverTextCol: Color = Color(0xfff066),
val mouseoverBackCol: Color = Color(0,0,0,0), val mouseoverBackCol: Color = Color(0,0,0,0),
val mouseoverBackBlendMode: String = BlendMode.NORMAL, val mouseoverBackBlendMode: String = BlendMode.NORMAL,
val inactiveTextCol: Color = UIItemTextButton.defaultInactiveCol,
val backCol: Color = Color(0,0,0,0), val backCol: Color = Color(0,0,0,0),
val backBlendMode: String = BlendMode.NORMAL, val backBlendMode: String = BlendMode.NORMAL,
var quickslot: Int? = null, var quickslot: Int? = null,
@@ -88,7 +89,7 @@ class UIItemInventoryElem(
// if mouse is over, text lights up // if mouse is over, text lights up
// this one-liner sets color // 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( g.drawString(
item!!.name + (if (amount > 0 && !item!!.isUnique) "${0x3000.toChar()}($amount)" else "") + item!!.name + (if (amount > 0 && !item!!.isUnique) "${0x3000.toChar()}($amount)" else "") +
(if (equippedSlot != null) " ${0xE081.toChar()}\$$equippedSlot" else ""), (if (equippedSlot != null) " ${0xE081.toChar()}\$$equippedSlot" else ""),

View File

@@ -57,20 +57,14 @@ internal object Inventory : ConsoleCommand {
private fun addItem(refId: Int, amount: Int = 1) { private fun addItem(refId: Int, amount: Int = 1) {
if (target != null) { if (target != null) {
target!!.inventory.add(ItemCodex[refId], amount) target!!.addItem(ItemCodex[refId], amount)
} }
} }
private fun equipItem(refId: Int) { private fun equipItem(refId: Int) {
if (target != null) { if (target != null) {
val item = ItemCodex[refId] val item = ItemCodex[refId]
target!!.equipItem(item)
// if the item does not exist, add it first
if (!target!!.inventory.hasItem(item)) {
target!!.inventory.add(item)
}
target!!.inventory.itemEquipped[item.equipPosition] = item
} }
} }

View File

@@ -29,6 +29,7 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
* Sorted by referenceID. * Sorted by referenceID.
*/ */
private val itemList = ArrayList<InventoryPair>() private val itemList = ArrayList<InventoryPair>()
init { init {
} }
@@ -135,8 +136,8 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
fun hasItem(item: InventoryItem) = hasItem(item.id) fun contains(item: InventoryItem) = contains(item.id)
fun hasItem(id: Int) = fun contains(id: Int) =
if (itemList.size == 0) if (itemList.size == 0)
false false
else else

View File

@@ -9,6 +9,7 @@ import net.torvald.terrarum.gameactors.ActorHumanoid
import net.torvald.terrarum.gameactors.faction.FactionFactory import net.torvald.terrarum.gameactors.faction.FactionFactory
import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.mapdrawer.FeaturesDrawer import net.torvald.terrarum.mapdrawer.FeaturesDrawer
import net.torvald.terrarum.tileproperties.Tile
import net.torvald.terrarum.to10bit import net.torvald.terrarum.to10bit
import net.torvald.terrarum.toInt import net.torvald.terrarum.toInt
import org.newdawn.slick.Color import org.newdawn.slick.Color
@@ -75,8 +76,15 @@ object PlayerBuilderSigrid {
// Test fill up inventory // Test fill up inventory
p.inventory.add(16, 512) val tiles = arrayOf(
p.equipItem(ItemCodex[16]) 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) }

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.gameactors
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameitem.InventoryItem import net.torvald.terrarum.gameitem.InventoryItem
import net.torvald.terrarum.itemproperties.ItemCodex
import java.util.* import java.util.*
/** /**
@@ -18,7 +19,7 @@ interface Pocketed {
if (item.equipPosition == InventoryItem.EquipPosition.NULL) if (item.equipPosition == InventoryItem.EquipPosition.NULL)
throw Error("Unequipping the item that cannot be equipped") 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") throw Error("Unequipping the item that does not exist in inventory")
inventory.itemEquipped[item.equipPosition] = null 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. * Equips an item. If the item is not in the inventory, adds the item first.
*/ */
fun equipItem(item: InventoryItem) { fun equipItem(item: InventoryItem) {
if (!inventory.hasItem(item)) if (!inventory.contains(item))
inventory.add(item) inventory.add(item)
if (item.equipPosition >= 0) { 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 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) { fun consumePrimary(item: InventoryItem) {

View File

@@ -16,7 +16,7 @@ class ThreadActorUpdate(val startIndex: Int, val endIndex: Int,
if (it is Pocketed) { if (it is Pocketed) {
it.inventory.forEach { inventoryEntry -> it.inventory.forEach { inventoryEntry ->
inventoryEntry.item.effectWhileInPocket(gc, delta) inventoryEntry.item.effectWhileInPocket(gc, delta)
if (it.isEquipped(inventoryEntry.item)) { if (it.equipped(inventoryEntry.item)) {
inventoryEntry.item.effectWhenEquipped(gc, delta) inventoryEntry.item.effectWhenEquipped(gc, delta)
} }
} }

View File

@@ -29,7 +29,7 @@ open abstract class DynamicItem(val baseItemID: Int?, newMass: Double? = null, n
var ret: Int var ret: Int
do { do {
ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID 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 return ret
} }

View File

@@ -33,6 +33,7 @@ class UIInventory(
val catButtonsToCatIdent = HashMap<String, String>() val catButtonsToCatIdent = HashMap<String, String>()
val backgroundColour = Color(0x80242424.toInt()) val backgroundColour = Color(0x80242424.toInt())
val defaultTextColour = Color(0xeaeaea)
init { init {
catButtonsToCatIdent.put("GAME_INVENTORY_WEAPONS", InventoryItem.Category.WEAPON) catButtonsToCatIdent.put("GAME_INVENTORY_WEAPONS", InventoryItem.Category.WEAPON)
@@ -79,11 +80,13 @@ class UIInventory(
defaultSelection = 0, defaultSelection = 0,
iconSpriteSheet = SpriteSheet("./assets/graphics/gui/inventory/category.tga", 20, 20), iconSpriteSheet = SpriteSheet("./assets/graphics/gui/inventory/category.tga", 20, 20),
iconSpriteSheetIndices = intArrayOf(9,6,7,0,1,2,3,4,5,8), iconSpriteSheetIndices = intArrayOf(9,6,7,0,1,2,3,4,5,8),
iconCol = defaultTextColour,
highlightBackCol = Color(0xb8b8b8), highlightBackCol = Color(0xb8b8b8),
highlightBackBlendMode = BlendMode.MULTIPLY, highlightBackBlendMode = BlendMode.MULTIPLY,
backgroundCol = Color(0,0,0,0), // will use custom background colour! backgroundCol = Color(0,0,0,0), // will use custom background colour!
backgroundBlendMode = BlendMode.NORMAL, backgroundBlendMode = BlendMode.NORMAL,
kinematic = true kinematic = true,
inactiveCol = defaultTextColour
) )
val itemsStripWidth = ((width - catButtons.width) - (2 * itemStripGutterH + itemInterColGutter)) / 2 val itemsStripWidth = ((width - catButtons.width) - (2 * itemStripGutterH + itemInterColGutter)) / 2
@@ -101,7 +104,8 @@ class UIInventory(
mouseoverBackBlendMode = BlendMode.SCREEN, mouseoverBackBlendMode = BlendMode.SCREEN,
backCol = Color(0xd4d4d4), backCol = Color(0xd4d4d4),
backBlendMode = BlendMode.MULTIPLY, backBlendMode = BlendMode.MULTIPLY,
drawBackOnNull = true drawBackOnNull = true,
inactiveTextCol = defaultTextColour
) }) ) })
val itemsScrollOffset = 0 val itemsScrollOffset = 0
@@ -234,7 +238,7 @@ class UIInventory(
// texts // texts
blendNormal() blendNormal()
g.color = Color(0xe8e8e8) g.color = defaultTextColour
// W - close // W - close
g.drawString(listControlClose, 4f, height - controlHelpHeight.toFloat()) g.drawString(listControlClose, 4f, height - controlHelpHeight.toFloat())
// MouseL - Use ; 1.9 - Register ; T - Drop // MouseL - Use ; 1.9 - Register ; T - Drop

View File

@@ -26,18 +26,19 @@ class UIItemTextButtonList(
val textAreaWidth: Int, val textAreaWidth: Int,
val iconSpriteSheet: SpriteSheet? = null, val iconSpriteSheet: SpriteSheet? = null,
val iconSpriteSheetIndices: IntArray? = null, val iconSpriteSheetIndices: IntArray? = null,
val iconCol: Color = UIItemTextButton.defaultInactiveCol,
// copied directly from UIItemTextButton // copied directly from UIItemTextButton
val activeCol: Color = Color(0xfff066), 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 activeBackBlendMode: String = BlendMode.NORMAL,
val highlightCol: Color = Color(0x00f8ff), val highlightCol: Color = Color(0x00f8ff),
val highlightBackCol: Color = Color(0xb0b0b0), val highlightBackCol: Color = Color(0xb0b0b0),
val highlightBackBlendMode: String = BlendMode.MULTIPLY, val highlightBackBlendMode: String = BlendMode.MULTIPLY,
val inactiveCol: Color = Color(0xc0c0c0), 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 backgroundBlendMode: String = BlendMode.NORMAL,
val kinematic: Boolean = false // more "kinetic" movement of selector val kinematic: Boolean = false
) : UIItem(parentUI) { ) : UIItem(parentUI) {
val iconToTextGap = 20 val iconToTextGap = 20
@@ -158,7 +159,8 @@ class UIItemTextButtonList(
iconSpriteSheetIndices!!.forEachIndexed { counter, imageIndex -> iconSpriteSheetIndices!!.forEachIndexed { counter, imageIndex ->
iconSpriteSheet.getSubImage(imageIndex, 0).draw( iconSpriteSheet.getSubImage(imageIndex, 0).draw(
32f, 32f,
buttons[counter].posY + iconY.toFloat() buttons[counter].posY + iconY.toFloat(),
iconCol
) )
} }
} }

View File

@@ -23,6 +23,11 @@ class UIVitalMetre(
val order: Int val order: Int
) : UICanvas { ) : UICanvas {
init {
// semitransparent
color?.a = 0.91f
}
private val margin = 25 private val margin = 25
private val gap = 4f private val gap = 4f
@@ -45,7 +50,12 @@ class UIVitalMetre(
private val theta = 33f private val theta = 33f
private val halfTheta = theta / 2f 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) { override fun update(gc: GameContainer, delta: Int) {
@@ -66,9 +76,11 @@ class UIVitalMetre(
) )
g.lineWidth = 2f g.lineWidth = 2f
val ratio = minOf(1f, vitalGetterVal()!! / vitalGetterMax()!!)
// background // background
g.color = backColor g.color = backColor
g.drawArc( g.drawArc(
@@ -77,7 +89,7 @@ class UIVitalMetre(
circleRadius * 2f + order * gap * 2, circleRadius * 2f + order * gap * 2,
circleRadius * 2f + order * gap * 2, circleRadius * 2f + order * gap * 2,
90f - halfTheta, 90f - halfTheta,
90f + halfTheta - theta * (vitalGetterVal()!! / vitalGetterMax()!!) 90f + halfTheta - theta * ratio
) )
g.color = color g.color = color
@@ -86,7 +98,7 @@ class UIVitalMetre(
-circleRadius - order * gap - offsetY, -circleRadius - order * gap - offsetY,
circleRadius * 2f + order * gap * 2, circleRadius * 2f + order * gap * 2,
circleRadius * 2f + order * gap * 2, circleRadius * 2f + order * gap * 2,
90f + halfTheta - theta * (vitalGetterVal()!! / vitalGetterMax()!!), 90f + halfTheta - theta * ratio,
90f + halfTheta 90f + halfTheta
) )

View File

@@ -199,7 +199,7 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) {
val file = computer.getFile(path) val file = computer.getFile(path)
try { try {
if (file!!.contents is EntryFile) if (file!!.contents is EntryFile)
return LuaValue.valueOf(file.contents.getSizePure()) return LuaValue.valueOf(file.contents.getSizePure().toInt())
else if (file.contents is EntryDirectory) else if (file.contents is EntryDirectory)
return LuaValue.valueOf(file.contents.entries.size) return LuaValue.valueOf(file.contents.entries.size)
} }
@@ -364,7 +364,7 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) {
when (mode) { when (mode) {
"r" -> { "r" -> {
try { 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["close"] = FileClassClose(fr)
luaClass["readLine"] = FileClassReadLine(fr) luaClass["readLine"] = FileClassReadLine(fr)
luaClass["readAll"] = FileClassReadAll(file) luaClass["readAll"] = FileClassReadAll(file)
@@ -381,7 +381,7 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) {
} }
"rb" -> { "rb" -> {
try { try {
val fis = ByteArrayInputStream(file.bytes) val fis = ByteArrayInputStream(file.bytes.toByteArray())
luaClass["close"] = FileClassClose(fis) luaClass["close"] = FileClassClose(fis)
luaClass["read"] = FileClassReadByte(fis) luaClass["read"] = FileClassReadByte(fis)
luaClass["readAll"] = FileClassReadAll(file) luaClass["readAll"] = FileClassReadAll(file)
@@ -492,13 +492,13 @@ internal class Filesystem(globals: Globals, computer: TerrarumComputer) {
private class FileClassReadAllBytes(val file: EntryFile) : ZeroArgFunction() { private class FileClassReadAllBytes(val file: EntryFile) : ZeroArgFunction() {
override fun call() : LuaValue { 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() { private class FileClassReadAll(val file: EntryFile) : ZeroArgFunction() {
override fun call() : LuaValue { override fun call() : LuaValue {
return LuaValue.valueOf(String(file.bytes, sysCharset)) return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset))
} }
} }

View File

@@ -43,7 +43,7 @@ class PeripheralVideoCard(val host: TerrarumComputer, val termW: Int = 80, val t
val width = termW * blockW val width = termW * blockW
val height = termH * blockH val height = termH * blockH
val spritesCount = 64 val spritesCount = 256
val vram = VRAM(width, height, spritesCount) val vram = VRAM(width, height, spritesCount)
val frameBuffer = ImageBuffer(width, height) val frameBuffer = ImageBuffer(width, height)

View File

@@ -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<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 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()
}
}
}

View File

@@ -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) { fun dumpToRealMachine(disk: VirtualDisk, outfile: File) {
if (!outfile.exists()) outfile.createNewFile() 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 * @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 { 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!") throw RuntimeException("Invalid Virtual Disk file!")
val diskSize = inbytes.sliceArray(4..7).toIntBig() val diskSize = inbytes.sliceArray(4L..9L).toInt48Big()
val diskName = inbytes.sliceArray(8..8 + 31) val diskName = inbytes.sliceArray(10L..10L + 31)
val diskCRC = inbytes.sliceArray(8 + 32..8 + 32 + 3).toIntBig() // to check with completed vdisk 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") //println("[VDUtil] currentUnixtime = $currentUnixtime")
var entryOffset = 44 var entryOffset = VirtualDisk.HEADER_SIZE
while (!Arrays.equals(inbytes.sliceArray(entryOffset..entryOffset + 3), VirtualDisk.FOOTER_START_MARK)) { while (!Arrays.equals(inbytes.sliceArray(entryOffset..entryOffset + 3).toByteArray(), VirtualDisk.FOOTER_START_MARK)) {
//println("[VDUtil] entryOffset = $entryOffset") //println("[VDUtil] entryOffset = $entryOffset")
// read and prepare all the shits // read and prepare all the shits
val entryIndexNum = inbytes.sliceArray(entryOffset..entryOffset + 3).toIntBig() val entryID = inbytes.sliceArray(entryOffset..entryOffset + 3).toIntBig()
val entryTypeFlag = inbytes[entryOffset + 4] val entryParentID = inbytes.sliceArray(entryOffset + 4..entryOffset + 7).toIntBig()
val entryFileName = inbytes.sliceArray(entryOffset + 5..entryOffset + 260) val entryTypeFlag = inbytes[entryOffset + 8]
val entryCreationTime = inbytes.sliceArray(entryOffset + 261..entryOffset + 268).toLongBig() val entryFileName = inbytes.sliceArray(entryOffset + 9..entryOffset + 9 + 255).toByteArray()
val entryModifyTime = inbytes.sliceArray(entryOffset + 269..entryOffset + 276).toLongBig() 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 entryCRC = inbytes.sliceArray(entryOffset + 277..entryOffset + 280).toIntBig() // to check with completed entry
val entryData = when (entryTypeFlag) { val entryData = when (entryTypeFlag) {
DiskEntry.NORMAL_FILE -> { 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") //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 -> { 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") //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 -> { 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 // update entryOffset so that we can fetch next entry in the binary
entryOffset += 281 + entryData.size + when (entryTypeFlag) { entryOffset += DiskEntry.HEADER_SIZE + entryData.size + when (entryTypeFlag) {
DiskEntry.NORMAL_FILE -> 4 DiskEntry.NORMAL_FILE -> 6
DiskEntry.DIRECTORY -> 2 DiskEntry.DIRECTORY -> 2
DiskEntry.SYMLINK -> 0 DiskEntry.SYMLINK -> 0
else -> throw RuntimeException("Unknown entry with type $entryTypeFlag") else -> throw RuntimeException("Unknown entry with type $entryTypeFlag")
@@ -146,7 +174,8 @@ object VDUtil {
// create entry // create entry
val diskEntry = DiskEntry( val diskEntry = DiskEntry(
entryID = entryIndexNum, entryID = entryID,
parentEntryID = entryParentID,
filename = entryFileName, filename = entryFileName,
creationDate = entryCreationTime, creationDate = entryCreationTime,
modificationDate = entryModifyTime, modificationDate = entryModifyTime,
@@ -173,7 +202,7 @@ object VDUtil {
val calculatedCRC = diskEntry.hashCode() val calculatedCRC = diskEntry.hashCode()
val crcMsg = "CRC failed: expected ${entryCRC.toHex()}, got ${calculatedCRC.toHex()}\n" + 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 (calculatedCRC != entryCRC) {
if (crcWarnLevel == Level.SEVERE) if (crcWarnLevel == Level.SEVERE)
@@ -184,7 +213,7 @@ object VDUtil {
} }
// add entry to disk // 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 = private fun DiskEntry.getAsNormalFile(disk: VirtualDisk): EntryFile =
this.contents as? EntryFile ?: this.contents as? EntryFile ?:
if (this.contents is EntryDirectory) if (this.contents is EntryDirectory)
throw RuntimeException("this is directory") throw RuntimeException("this is directory")
else if (this.contents is EntrySymlink) else if (this.contents is EntrySymlink)
disk.entries[this.contents.target]!!.getAsNormalFile(disk) disk.entries[this.contents.target]!!.getAsNormalFile(disk)
else else
throw RuntimeException("Unknown entry type") throw RuntimeException("Unknown entry type")
/** /**
* SYNOPSIS disk.getFile("bin/msh.lua")!!.first.getAsNormalFile(disk) * SYNOPSIS disk.getFile("bin/msh.lua")!!.first.getAsNormalFile(disk)
* *
@@ -307,12 +336,12 @@ object VDUtil {
*/ */
private fun DiskEntry.getAsDirectory(disk: VirtualDisk): EntryDirectory = private fun DiskEntry.getAsDirectory(disk: VirtualDisk): EntryDirectory =
this.contents as? EntryDirectory ?: this.contents as? EntryDirectory ?:
if (this.contents is EntrySymlink) if (this.contents is EntrySymlink)
disk.entries[this.contents.target]!!.getAsDirectory(disk) disk.entries[this.contents.target]!!.getAsDirectory(disk)
else if (this.contents is EntryFile) else if (this.contents is EntryFile)
throw RuntimeException("this is not directory") throw RuntimeException("this is not directory")
else else
throw RuntimeException("Unknown entry type") throw RuntimeException("Unknown entry type")
/** /**
* Search for the file and returns a instance of normal file. * Search for the file and returns a instance of normal file.
@@ -342,20 +371,26 @@ object VDUtil {
val fileSearchResult = getFile(disk, path)!! 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. * Deletes file on the disk safely.
*/ */
fun deleteFile(disk: VirtualDisk, parentID: EntryID, targetID: EntryID) { fun deleteFile(disk: VirtualDisk, targetID: EntryID) {
disk.checkReadOnly() disk.checkReadOnly()
val file = disk.entries[targetID] val file = disk.entries[targetID]
if (file == null) {
throw FileNotFoundException("No such file to delete")
}
val parentID = file.parentEntryID
val parentDir = disk.entries[parentID] val parentDir = disk.entries[parentID]
fun rollback() { fun rollback() {
if (!disk.entries.contains(targetID)) { if (!disk.entries.contains(targetID)) {
disk.entries[targetID] = file!! disk.entries[targetID] = file
} }
if (!(parentDir!!.contents as EntryDirectory).entries.contains(targetID)) { if (!(parentDir!!.contents as EntryDirectory).entries.contains(targetID)) {
(parentDir.contents as EntryDirectory).entries.add(targetID) (parentDir.contents as EntryDirectory).entries.add(targetID)
@@ -365,14 +400,16 @@ object VDUtil {
if (parentDir == null || parentDir.contents !is EntryDirectory) { if (parentDir == null || parentDir.contents !is EntryDirectory) {
throw FileNotFoundException("No such parent directory") 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") throw FileNotFoundException("No such file to delete")
} }
else if (targetID == 0) { else if (targetID == 0) {
throw IOException("Cannot delete root file system") throw IOException("Cannot delete root file system")
} }
else if (file.contents is EntryDirectory && file.contents.entries.size > 0) { 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 { else {
try { 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) { fun addFile(disk: VirtualDisk, parentPath: VDPath, file: DiskEntry) {
disk.checkReadOnly() disk.checkReadOnly()
disk.checkCapacity(file.serialisedSize) disk.checkCapacity(file.serialisedSize)
try { try {
val parentID = getFile(disk, parentPath)!!.file.entryID
// add record to the directory // add record to the directory
getAsDirectory(disk, parentPath).entries.add(file.entryID) getAsDirectory(disk, parentPath).entries.add(file.entryID)
// add entry on the disk // add entry on the disk
disk.entries[file.entryID] = file disk.entries[file.entryID] = file
file.parentEntryID = parentID
} }
catch (e: KotlinNullPointerException) { catch (e: KotlinNullPointerException) {
throw FileNotFoundException("No such directory") 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) { fun addFile(disk: VirtualDisk, directoryID: EntryID, file: DiskEntry) {
disk.checkReadOnly() disk.checkReadOnly()
@@ -442,6 +481,7 @@ object VDUtil {
getAsDirectory(disk, directoryID).entries.add(file.entryID) getAsDirectory(disk, directoryID).entries.add(file.entryID)
// add entry on the disk // add entry on the disk
disk.entries[file.entryID] = file disk.entries[file.entryID] = file
file.parentEntryID = directoryID
} }
catch (e: KotlinNullPointerException) { catch (e: KotlinNullPointerException) {
throw FileNotFoundException("No such directory") throw FileNotFoundException("No such directory")
@@ -457,11 +497,13 @@ object VDUtil {
val newID = disk.generateUniqueID() val newID = disk.generateUniqueID()
try { try {
val parentID = getFile(disk, parentPath)!!.file.entryID
// add record to the directory // add record to the directory
getAsDirectory(disk, parentPath).entries.add(newID) getAsDirectory(disk, parentPath).entries.add(newID)
// add entry on the disk // add entry on the disk
disk.entries[newID] = DiskEntry( disk.entries[newID] = DiskEntry(
newID, newID,
parentID,
name, name,
currentUnixtime, currentUnixtime,
currentUnixtime, currentUnixtime,
@@ -487,6 +529,7 @@ object VDUtil {
// add entry on the disk // add entry on the disk
disk.entries[newID] = DiskEntry( disk.entries[newID] = DiskEntry(
newID, newID,
directoryID,
name, name,
currentUnixtime, currentUnixtime,
currentUnixtime, currentUnixtime,
@@ -498,6 +541,45 @@ object VDUtil {
} }
} }
fun deleteDirRecurse(disk: VirtualDisk, directoryID: EntryID) {
val entriesToDelete = ArrayList<EntryID>()
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. * Imports external file and returns corresponding DiskEntry.
@@ -509,10 +591,11 @@ object VDUtil {
return DiskEntry( return DiskEntry(
entryID = id, entryID = id,
parentEntryID = 0, // placeholder
filename = file.name.toByteArray(), filename = file.name.toByteArray(),
creationDate = currentUnixtime, creationDate = currentUnixtime,
modificationDate = currentUnixtime, modificationDate = currentUnixtime,
contents = EntryFile(file.readBytes()) contents = EntryFile(file.readBytes64())
) )
} }
/** /**
@@ -520,7 +603,7 @@ object VDUtil {
*/ */
fun exportFile(entryFile: EntryFile, outfile: File) { fun exportFile(entryFile: EntryFile, outfile: File) {
outfile.createNewFile() outfile.createNewFile()
outfile.writeBytes(entryFile.bytes) outfile.writeBytes64(entryFile.bytes)
} }
/** /**
@@ -557,7 +640,7 @@ object VDUtil {
try { try {
deleteFile(disk2, toPath) 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 deleteFile(disk1, fromPath) // any uncaught no_from_file will be caught here
try { try {
@@ -578,10 +661,11 @@ object VDUtil {
/** /**
* Creates new disk with given name and capacity * 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 newdisk = VirtualDisk(diskSize, diskName.toEntryName(VirtualDisk.NAME_LENGTH, charset))
val rootDir = DiskEntry( val rootDir = DiskEntry(
entryID = 0, entryID = 0,
parentEntryID = 0,
filename = DiskEntry.ROOTNAME.toByteArray(charset), filename = DiskEntry.ROOTNAME.toByteArray(charset),
creationDate = currentUnixtime, creationDate = currentUnixtime,
modificationDate = currentUnixtime, modificationDate = currentUnixtime,
@@ -595,12 +679,13 @@ object VDUtil {
/** /**
* Creates new zero-filled file with given name and size * 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.checkReadOnly()
disk.checkCapacity(fileSize + DiskEntry.HEADER_SIZE + 4) disk.checkCapacity(fileSize + DiskEntry.HEADER_SIZE + 4)
addFile(disk, directoryID, DiskEntry( addFile(disk, directoryID, DiskEntry(
disk.generateUniqueID(), disk.generateUniqueID(),
directoryID,
filename.toEntryName(DiskEntry.NAME_LENGTH, charset = charset), filename.toEntryName(DiskEntry.NAME_LENGTH, charset = charset),
currentUnixtime, currentUnixtime,
currentUnixtime, currentUnixtime,
@@ -619,29 +704,31 @@ object VDUtil {
/** /**
* Throws an exception if specified size cannot fit into the disk * 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) if (this.usedBytes + newSize > this.capacity)
throw IOException("Not enough space on the disk") throw IOException("Not enough space on the disk")
} }
fun ByteArray.toIntBig(): Int { fun ByteArray64.toIntBig(): Int {
if (this.size != 4) if (this.size != 4L)
throw OperationNotSupportedException("ByteArray is not Int") throw OperationNotSupportedException("ByteArray is not Int")
var i = 0 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 return i
} }
fun ByteArray.toLongBig(): Long { fun ByteArray64.toInt48Big(): Long {
if (this.size != 8) if (this.size != 6L)
throw OperationNotSupportedException("ByteArray is not Long") throw OperationNotSupportedException("ByteArray is not Long")
var i = 0L 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 return i
} }
fun ByteArray.toShortBig(): Short { fun ByteArray64.toShortBig(): Short {
if (this.size != 2) if (this.size != 2L)
throw OperationNotSupportedException("ByteArray is not Long") throw OperationNotSupportedException("ByteArray is not Short")
return (this[0].toUint().shl(256) + this[1].toUint()).toShort() return (this[0].toUint().shl(256) + this[1].toUint()).toShort()
} }
@@ -685,17 +772,66 @@ object VDUtil {
return dir.contents.entries.contains(targetID) 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<EntryID> {
return disk.entries.filter { disk.entries[it.value.parentEntryID] == null }.keys.toList()
}
fun gcSearchPhantomBaby(disk: VirtualDisk): List<Pair<EntryID, EntryID>> {
// Pair<DirectoryID, ID of phantom in the directory>
val phantoms = ArrayList<Pair<EntryID, EntryID>>()
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 Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean { 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 { fun String.toEntryName(length: Int, charset: Charset): ByteArray {
val buffer = AppendableByteBuffer(length) val buffer = AppendableByteBuffer(length.toLong())
val stringByteArray = this.toByteArray(charset) val stringByteArray = this.toByteArray(charset)
buffer.put(stringByteArray.sliceArray(0..minOf(length, stringByteArray.size) - 1)) buffer.put(stringByteArray.sliceArray(0..minOf(length, stringByteArray.size) - 1))
return buffer.array return buffer.array.toByteArray()
} }
fun ByteArray.toCanonicalString(charset: Charset): String { fun ByteArray.toCanonicalString(charset: Charset): String {
var lastIndexOfRealStr = 0 var lastIndexOfRealStr = 0
@@ -708,9 +844,26 @@ fun ByteArray.toCanonicalString(charset: Charset): String {
return String(this.sliceArray(0..lastIndexOfRealStr), charset) return String(this.sliceArray(0..lastIndexOfRealStr), charset)
} }
fun ArrayList<Byte>.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 * Writes String to the file
* *
* Note: this FileWriter cannot write more than 2 GiB
*
* @param fileEntry must be File, resolve symlink beforehand * @param fileEntry must be File, resolve symlink beforehand
* @param mode "w" or "a" * @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) { override fun write(cbuf: CharArray, off: Int, len: Int) {
if (!closed) { if (!closed) {
val newByteArray = String(cbuf).toByteArray(charset) val newByteArray = String(cbuf).toByteArray(charset).toByteArray64()
newFileBuffer.addAll(newByteArray.asIterable()) newByteArray.forEach { newFileBuffer.add(it) }
} }
else { else {
throw IOException() throw IOException()
@@ -741,16 +894,16 @@ class VDFileWriter(private val fileEntry: DiskEntry, private val append: Boolean
val newByteArray = newFileBuffer.toByteArray() val newByteArray = newFileBuffer.toByteArray()
if (!append) { if (!append) {
(fileEntry.contents as EntryFile).bytes = newByteArray (fileEntry.contents as EntryFile).bytes = newByteArray.toByteArray64()
} }
else { 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) val newFileBuffer = ByteArray(oldByteArray.size + newByteArray.size)
System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size) System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size)
System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size) System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size)
(fileEntry.contents as EntryFile).bytes = newByteArray fileEntry.contents.bytes = newByteArray.toByteArray64()
} }
newFileBuffer = ArrayList<Byte>() newFileBuffer = ArrayList<Byte>()
@@ -788,16 +941,16 @@ class VDFileOutputStream(private val fileEntry: DiskEntry, private val append: B
val newByteArray = newFileBuffer.toByteArray() val newByteArray = newFileBuffer.toByteArray()
if (!append) { if (!append) {
(fileEntry.contents as EntryFile).bytes = newByteArray (fileEntry.contents as EntryFile).bytes = newByteArray.toByteArray64()
} }
else { 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) val newFileBuffer = ByteArray(oldByteArray.size + newByteArray.size)
System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size) System.arraycopy(oldByteArray, 0, newFileBuffer, 0, oldByteArray.size)
System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size) System.arraycopy(newByteArray, 0, newFileBuffer, oldByteArray.size, newByteArray.size)
(fileEntry.contents as EntryFile).bytes = newByteArray fileEntry.contents.bytes = newByteArray.toByteArray64()
} }
newFileBuffer = ArrayList<Byte>() newFileBuffer = ArrayList<Byte>()

View File

@@ -11,27 +11,31 @@ import java.util.zip.CRC32
typealias EntryID = Int typealias EntryID = Int
val specversion = 0x02.toByte()
class VirtualDisk( class VirtualDisk(
/** capacity of 0 makes the disk read-only */ /** capacity of 0 makes the disk read-only */
var capacity: Int, var capacity: Long,
var diskName: ByteArray = ByteArray(NAME_LENGTH) var diskName: ByteArray = ByteArray(NAME_LENGTH)
) { ) {
val entries = HashMap<EntryID, DiskEntry>() val entries = HashMap<EntryID, DiskEntry>()
val isReadOnly: Boolean val isReadOnly: Boolean
get() = capacity == 0 get() = capacity == 0L
fun getDiskNameString(charset: Charset) = String(diskName, charset) fun getDiskNameString(charset: Charset) = String(diskName, charset)
val root: DiskEntry val root: DiskEntry
get() = entries[0]!! get() = entries[0]!!
private fun serializeEntriesOnly(): ByteArray { private fun serializeEntriesOnly(): ByteArray64 {
val bufferList = ArrayList<Byte>() val bufferList = ArrayList<Byte>()
entries.forEach { entries.forEach {
val serialised = it.value.serialize() val serialised = it.value.serialize()
serialised.forEach { bufferList.add(it) } 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 { fun serialize(): AppendableByteBuffer {
@@ -40,9 +44,10 @@ class VirtualDisk(
val crc = hashCode().toBigEndian() val crc = hashCode().toBigEndian()
buffer.put(MAGIC) buffer.put(MAGIC)
buffer.put(capacity.toBigEndian()) buffer.put(capacity.toInt48())
buffer.put(diskName.forceSize(NAME_LENGTH)) buffer.put(diskName.forceSize(NAME_LENGTH))
buffer.put(crc) buffer.put(crc)
buffer.put(specversion)
buffer.put(entriesBuffer) buffer.put(entriesBuffer)
buffer.put(FOOTER_START_MARK) buffer.put(FOOTER_START_MARK)
buffer.put(EOF_MARK) buffer.put(EOF_MARK)
@@ -53,7 +58,7 @@ class VirtualDisk(
override fun hashCode(): Int { override fun hashCode(): Int {
val crcList = IntArray(entries.size) val crcList = IntArray(entries.size)
var crcListAppendCursor = 0 var crcListAppendCursor = 0
entries.forEach { t, u -> entries.forEach { _, u ->
crcList[crcListAppendCursor] = u.hashCode() crcList[crcListAppendCursor] = u.hashCode()
crcListAppendCursor++ crcListAppendCursor++
} }
@@ -65,7 +70,7 @@ class VirtualDisk(
} }
/** Expected size of the virtual disk */ /** Expected size of the virtual disk */
val usedBytes: Int val usedBytes: Long
get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE
fun generateUniqueID(): Int { fun generateUniqueID(): Int {
@@ -80,8 +85,8 @@ class VirtualDisk(
override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})" override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})"
companion object { companion object {
val HEADER_SIZE = 44 // according to the spec val HEADER_SIZE = 47L // according to the spec
val FOOTER_SIZE = 6 // footer mark + EOF val FOOTER_SIZE = 6L // footer mark + EOF
val NAME_LENGTH = 32 val NAME_LENGTH = 32
val MAGIC = "TEVd".toByteArray() val MAGIC = "TEVd".toByteArray()
@@ -95,6 +100,7 @@ class VirtualDisk(
class DiskEntry( class DiskEntry(
// header // header
var entryID: EntryID, var entryID: EntryID,
var parentEntryID: EntryID,
var filename: ByteArray = ByteArray(NAME_LENGTH), var filename: ByteArray = ByteArray(NAME_LENGTH),
var creationDate: Long, var creationDate: Long,
var modificationDate: Long, var modificationDate: Long,
@@ -104,11 +110,11 @@ class DiskEntry(
) { ) {
fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset) fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset)
val serialisedSize: Int val serialisedSize: Long
get() = contents.getSizeEntry() + HEADER_SIZE get() = contents.getSizeEntry() + HEADER_SIZE
companion object { companion object {
val HEADER_SIZE = 281 // according to the spec val HEADER_SIZE = 281L // according to the spec
val ROOTNAME = "(root)" val ROOTNAME = "(root)"
val NAME_LENGTH = 256 val NAME_LENGTH = 256
@@ -131,13 +137,14 @@ class DiskEntry(
fun serialize(): AppendableByteBuffer { fun serialize(): AppendableByteBuffer {
val serialisedContents = contents.serialize() val serialisedContents = contents.serialize()
val buffer = AppendableByteBuffer(281 + serialisedContents.size) val buffer = AppendableByteBuffer(HEADER_SIZE + serialisedContents.size)
buffer.put(entryID.toBigEndian()) buffer.put(entryID.toBigEndian())
buffer.put(parentEntryID.toBigEndian())
buffer.put(contents.getTypeFlag()) buffer.put(contents.getTypeFlag())
buffer.put(filename.forceSize(NAME_LENGTH)) buffer.put(filename.forceSize(NAME_LENGTH))
buffer.put(creationDate.toBigEndian()) buffer.put(creationDate.toInt48())
buffer.put(modificationDate.toBigEndian()) buffer.put(modificationDate.toInt48())
buffer.put(this.hashCode().toBigEndian()) buffer.put(this.hashCode().toBigEndian())
buffer.put(serialisedContents.array) buffer.put(serialisedContents.array)
@@ -157,27 +164,27 @@ fun ByteArray.forceSize(size: Int): ByteArray {
} }
interface DiskEntryContent { interface DiskEntryContent {
fun serialize(): AppendableByteBuffer fun serialize(): AppendableByteBuffer
fun getSizePure(): Int fun getSizePure(): Long
fun getSizeEntry(): Int fun getSizeEntry(): Long
} }
class EntryFile(var bytes: ByteArray) : DiskEntryContent { class EntryFile(var bytes: ByteArray64) : DiskEntryContent {
override fun getSizePure() = bytes.size override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 4 override fun getSizeEntry() = getSizePure() + 6
/** Create new blank file */ /** Create new blank file */
constructor(size: Int): this(ByteArray(size)) constructor(size: Long): this(ByteArray64(size))
override fun serialize(): AppendableByteBuffer { override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry()) val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(getSizePure().toBigEndian()) buffer.put(getSizePure().toInt48())
buffer.put(bytes) buffer.put(bytes)
return buffer return buffer
} }
} }
class EntryDirectory(val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent { class EntryDirectory(val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
override fun getSizePure() = entries.size * 4 override fun getSizePure() = entries.size * 4L
override fun getSizeEntry() = getSizePure() + 2 override fun getSizeEntry() = getSizePure() + 2
override fun serialize(): AppendableByteBuffer { override fun serialize(): AppendableByteBuffer {
@@ -188,13 +195,13 @@ class EntryDirectory(val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : D
} }
companion object { companion object {
val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4 val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4L
} }
} }
class EntrySymlink(val target: EntryID) : DiskEntryContent { class EntrySymlink(val target: EntryID) : DiskEntryContent {
override fun getSizePure() = 4 override fun getSizePure() = 4L
override fun getSizeEntry() = 4 override fun getSizeEntry() = 4L
override fun serialize(): AppendableByteBuffer { override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(4) 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 { fun Int.toBigEndian(): ByteArray {
return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() }) return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() })
} }
fun Long.toBigEndian(): ByteArray { fun Long.toInt48(): ByteArray {
return ByteArray(8, { this.ushr(56 - (8 * it)).toByte() }) return ByteArray(6, { this.ushr(40 - (8 * it)).toByte() })
} }
fun Short.toBigEndian(): ByteArray { fun Short.toBigEndian(): ByteArray {
return byteArrayOf( return byteArrayOf(
@@ -217,24 +223,24 @@ fun Short.toBigEndian(): ByteArray {
this.toByte() this.toByte()
) )
} }
fun AppendableByteBuffer.getCRC32(): Int { fun AppendableByteBuffer.getCRC32(): Int {
val crc = CRC32() val crc = CRC32()
crc.update(this.array) this.array.forEachInt32 { crc.update(it) }
return crc.value.toInt() return crc.value.toInt()
} }
class AppendableByteBuffer(val size: Int) { class AppendableByteBuffer(val size: Long) {
val array = ByteArray(size, { 0.toByte() }) val array = ByteArray64(size)
private var offset = 0 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 { fun put(byteArray: ByteArray): AppendableByteBuffer {
System.arraycopy( byteArray.forEach { put(it) }
byteArray, // source
0, // source pos
array, // destination
offset, // destination pos
byteArray.size // length
)
offset += byteArray.size
return this return this
} }
fun put(byte: Byte): AppendableByteBuffer { fun put(byte: Byte): AppendableByteBuffer {