mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-11 11:04:05 +09:00
inventory ui update
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ""),
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
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) {
|
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>()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user