inventory ui update

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

View File

@@ -96,14 +96,14 @@ class StateInGame : BasicGameState() {
// UI aliases (no pause)
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)
}
}

View File

@@ -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 ""),

View File

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

View File

@@ -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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,128 @@
package net.torvald.terrarum.virtualcomputer.tvd
import java.io.File
import java.io.FileOutputStream
/**
* ByteArray that can hold larger than 4 GiB of Data.
*
* Works kind of like Bank Switching of old game console's cartridges which does same thing.
*
* Created by Minjaesong on 2017-04-12.
*/
class ByteArray64(val size: Long) {
private val bankSize: Int = 1 shl 30 // 2^30 Bytes, or 1 GiB
private val data: Array<ByteArray>
init {
if (size <= 0)
throw IllegalArgumentException("Invalid array size!")
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
data = Array<ByteArray>(
requiredBanks,
{ bankIndex ->
kotlin.ByteArray(
if (bankIndex == requiredBanks - 1)
size.toBankOffset()
else
bankSize,
{ 0.toByte() }
)
}
)
}
private fun Long.toBankNumber(): Int = (this / bankSize).toInt()
private fun Long.toBankOffset(): Int = (this % bankSize).toInt()
operator fun set(index: Long, value: Byte) {
if (index < 0 || index >= size)
throw ArrayIndexOutOfBoundsException("size $size, index $index")
data[index.toBankNumber()][index.toBankOffset()] = value
}
operator fun get(index: Long): Byte {
if (index < 0 || index >= size)
throw ArrayIndexOutOfBoundsException("size $size, index $index")
return data[index.toBankNumber()][index.toBankOffset()]
}
operator fun iterator(): ByteIterator {
return object : ByteIterator() {
var iterationCounter = 0L
override fun nextByte(): Byte {
iterationCounter += 1
return this@ByteArray64[iterationCounter - 1]
}
override fun hasNext() = iterationCounter < this@ByteArray64.size
}
}
fun iteratorChoppedToInt(): IntIterator {
return object : IntIterator() {
var iterationCounter = 0L
val iteratorSize = 1 + ((this@ByteArray64.size - 1) / 4).toInt()
override fun nextInt(): Int {
var byteCounter = iterationCounter * 4L
var int = 0
(0..3).forEach {
if (byteCounter + it < this@ByteArray64.size) {
int += this@ByteArray64[byteCounter + it].toInt() shl (it * 8)
}
else {
int += 0 shl (it * 8)
}
}
iterationCounter += 1
return int
}
override fun hasNext() = iterationCounter < iteratorSize
}
}
fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) }
fun sliceArray(range: LongRange): ByteArray64 {
val newarr = ByteArray64(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index]
}
return newarr
}
fun toByteArray(): ByteArray {
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
throw TypeCastException("Impossible cast; too large to fit")
return ByteArray(this.size.toInt(), { this[it.toLong()] })
}
fun writeToFile(file: File) {
var fos = FileOutputStream(file, false)
fos.write(data[0])
fos.flush()
fos.close()
if (data.size > 1) {
fos = FileOutputStream(file, true)
for (i in 1..data.lastIndex) {
fos.write(data[i])
fos.flush()
}
fos.close()
}
}
}

View File

@@ -81,9 +81,29 @@ object VDUtil {
}
}
fun File.writeBytes64(array: ByteArray64) {
array.writeToFile(this)
}
fun File.readBytes64(): ByteArray64 {
val inbytes = ByteArray64(this.length())
val inputStream = BufferedInputStream(FileInputStream(this))
var readInt = inputStream.read()
var readInCounter = 0L
while (readInt != -1) {
inbytes[readInCounter] = readInt.toByte()
readInCounter += 1
readInt = inputStream.read()
}
inputStream.close()
return inbytes
}
fun dumpToRealMachine(disk: VirtualDisk, outfile: File) {
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>()

View File

@@ -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 {