mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
inventory ui update
This commit is contained in:
@@ -96,14 +96,14 @@ class StateInGame : BasicGameState() {
|
||||
|
||||
// UI aliases (no pause)
|
||||
val uiAliases = HashMap<String, UIHandler>()
|
||||
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<String, UIHandler>()
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ""),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
|
||||
* Sorted by referenceID.
|
||||
*/
|
||||
private val itemList = ArrayList<InventoryPair>()
|
||||
|
||||
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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class UIInventory(
|
||||
val catButtonsToCatIdent = HashMap<String, String>()
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
128
src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt
Normal file
128
src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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.
|
||||
@@ -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<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 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<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
|
||||
*
|
||||
* 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<Byte>()
|
||||
@@ -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<Byte>()
|
||||
|
||||
@@ -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<EntryID, DiskEntry>()
|
||||
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<Byte>()
|
||||
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<EntryID> = ArrayList<EntryID>()) : 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<EntryID> = ArrayList<EntryID>()) : 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 {
|
||||
|
||||
Reference in New Issue
Block a user