ModMgr: I can load class by name; dropped Groovy script support, coding must go to JAR

This commit is contained in:
minjaesong
2018-05-09 05:34:39 +09:00
parent d70aabc1b4
commit f0907d9737
26 changed files with 383 additions and 73 deletions

View File

@@ -12,7 +12,7 @@
"speed": 3.0,
"speedmult": [100,100,100,100,100,100,100],
"jumppower": 4.3,
"jumppower": 13.0,
"jumppowermult": [100,100,100,100,100,100,100],
"scale": 1.0,

View File

@@ -1,2 +1,2 @@
"id";"filename"
"8448";"testpick.groovy"
"id";"classname"
"8448";"net.torvald.terrarum.modulebasegame.items.PickaxeGeneric"
1 id filename classname
2 8448 testpick.groovy net.torvald.terrarum.modulebasegame.items.PickaxeGeneric

View File

@@ -6,6 +6,7 @@ import net.torvald.terrarum.utils.CSVFetcher
import net.torvald.terrarum.itemproperties.GameItem
import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.itemproperties.ItemID
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.utils.JsonFetcher
import org.apache.commons.csv.CSVFormat
@@ -182,12 +183,14 @@ object ModMgr {
@JvmStatic operator fun invoke(module: String) {
val csv = CSVFetcher.readFromModule(module, itemPath + "itemid.csv")
csv.forEach {
val filename = it["filename"].toString()
val script = getFile(module, itemPath + filename).readText()
val className = it["classname"].toString()
val itemID = it["id"].toInt()
groovyEngine.eval(script)
ItemCodex[itemID] = groovyInvocable.invokeFunction("invoke", itemID) as GameItem
val loadedClass = Class.forName(className)
val loadedClassConstructor = loadedClass.getConstructor(ItemID::class.java)
val loadedClassInstance = loadedClassConstructor.newInstance(itemID)
ItemCodex[itemID] = loadedClassInstance as GameItem
}
}
}

View File

@@ -0,0 +1,40 @@
package net.torvald.terrarum.audio
/**
* Mixes spacial audio sources to multiple channels
*
*
* Channels and their mapping:
*
* Notation: (front/side/rear.subwoofer/top-front)
* Plugs: G-Front (green), K-Rear (black), Y-Centre/Subwoofer (yellow), E-Side (grey)
* e.g. E-RC,NULL means GREY jack outputs REAR-CENTRE to its left and nothing to its right channel.
*
* = Headphones: Binaural
* = Stereo ---------- (2/0/0.0/0): G-FL,FR
* = Quadraphonic ---- (2/0/2.0/0): G-FL,FR; K-RL,RR
* = 4.0 ------------- (3/0/1.0/0): G-FL,FR; Y-FC,RC
* = 5.1 ------------- (3/0/2.1/0): G-FL,FR; Y-FC,SW; K-RL,RR
* = 6.1 ------------- (3/0/3.1/0): G-FL,FR; Y-FC,SW; K-RL,RR, E-RC,RC
* = 7.1 ------------- (3/2/2.1/0): G-FL,FR; Y-FC,SW; K-RL,RR, E-SL,SR
* = Dolby Atmos 5.1.2 (3/0/2.1/2): G-FL,FR; Y-FC,SW; K-RL,RR, E-TL,TR
*
*
* Channel uses:
*
* - Front and centre: usual thingies
* - Rear: weather/ambient if 4.0, channel phantoming otherwise
* - Top/Side: weather/ambient
* - Side: extreme pan for front channels
* - Centre: interface/UI
*
* * If both side and rear speakers are not there, play weather/ambient to the stereo speakers but NOT TO THE CENTRE
* * For non-existent speakers, use channel phantoming
*
* Note: 5.1.2 does NOT output Dolby-compatible signals.
*/
object SpatialAudioMixer {
const val centreQuotient = 0.7071f
}

View File

@@ -119,7 +119,7 @@ open class ActorHumanoid(
/** how long the walk button has down, in frames */
internal var walkCounterX = 0
internal var walkCounterY = 0
@Transient private val MAX_JUMP_LENGTH = 31 // manages "heaviness" of the jump control. Higher = heavier
@Transient private val MAX_JUMP_LENGTH = 25 // manages "heaviness" of the jump control. Higher = heavier
private var readonly_totalX = 0.0
private var readonly_totalY = 0.0

View File

@@ -34,7 +34,7 @@ object PlayerBuilderSigrid {
p.actorValue[AVKey.SPEEDBUFF] = 1.0
p.actorValue[AVKey.ACCEL] = ActorHumanoid.WALK_ACCEL_BASE
p.actorValue[AVKey.ACCELBUFF] = 1.0
p.actorValue[AVKey.JUMPPOWER] = 10.0
p.actorValue[AVKey.JUMPPOWER] = 13.0
p.actorValue[AVKey.BASEMASS] = 80.0
p.actorValue[AVKey.SCALEBUFF] = 1.0 // Constant 1.0 for player, meant to be used by random mobs

View File

@@ -1,22 +1,27 @@
package net.torvald.terrarum.virtualcomputer.tvd
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
/**
* ByteArray that can hold larger than 4 GiB of Data.
* ByteArray that can hold larger than 2 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
companion object {
val bankSize: Int = 8192
}
private val data: Array<ByteArray>
init {
if (size <= 0)
if (size < 0)
throw IllegalArgumentException("Invalid array size!")
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
@@ -94,8 +99,9 @@ class ByteArray64(val size: Long) {
fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) }
fun forEachBanks(consumer: (ByteArray) -> Unit) = data.forEach(consumer)
fun sliceArray(range: LongRange): ByteArray64 {
fun sliceArray64(range: LongRange): ByteArray64 {
val newarr = ByteArray64(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index]
@@ -103,6 +109,14 @@ class ByteArray64(val size: Long) {
return newarr
}
fun sliceArray(range: IntRange): ByteArray {
val newarr = ByteArray(range.last - range.first + 1)
range.forEach { index ->
newarr[index - range.first] = this[index.toLong()]
}
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")
@@ -125,4 +139,19 @@ class ByteArray64(val size: Long) {
fos.close()
}
}
}
class ByteArray64InputStream(val byteArray64: ByteArray64): InputStream() {
private var readCounter = 0L
override fun read(): Int {
readCounter += 1
return try {
byteArray64[readCounter - 1].toUint()
}
catch (e: ArrayIndexOutOfBoundsException) {
-1
}
}
}

View File

@@ -0,0 +1,102 @@
package net.torvald.terrarum.virtualcomputer.tvd
import java.io.File
import java.io.FileInputStream
/**
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
*
* Created by minjaesong on 2017-11-17.
*/
class DiskSkimmer(diskFile: File) {
class EntryOffsetPair(val entryID: Int, val offset: Long)
val entryToOffsetTable = ArrayList<EntryOffsetPair>()
init {
val fis = FileInputStream(diskFile)
var currentPosition = fis.skip(47) // skip disk header
fun skipRead(bytes: Long) {
currentPosition += fis.skip(bytes)
}
/**
* Reads a byte and adds up the position var
*/
fun readByte(): Byte {
currentPosition++
val read = fis.read()
if (read < 0) throw InternalError("Unexpectedly reached EOF")
return read.toByte()
}
/**
* Reads specific bytes to the buffer and adds up the position var
*/
fun readBytes(buffer: ByteArray): Int {
val readStatus = fis.read(buffer)
currentPosition += readStatus
return readStatus
}
fun readIntBig(): Int {
val buffer = ByteArray(4)
val readStatus = readBytes(buffer)
if (readStatus != 4) throw InternalError("Unexpected error -- EOF reached? (expected 4, got $readStatus)")
return buffer.toIntBig()
}
fun readInt48(): Long {
val buffer = ByteArray(6)
val readStatus = readBytes(buffer)
if (readStatus != 6) throw InternalError("Unexpected error -- EOF reached? (expected 6, got $readStatus)")
return buffer.toInt48()
}
while (true) {
val entryID = readIntBig()
// footer
if (entryID == 0xFEFEFEFE.toInt()) break
// fill up table
entryToOffsetTable.add(EntryOffsetPair(entryID, currentPosition))
skipRead(4) // skip entryID of parent
val entryType = readByte()
skipRead(256 + 6 + 6 + 4) // skips rest of the header
// figure out the entry size so that we can skip
val entrySize: Long = when(entryType) {
0x01.toByte() -> readInt48()
0x11.toByte() -> readInt48() + 6 // size of compressed payload + 6 (header elem for uncompressed size)
0x02.toByte() -> readIntBig().shl(16).toLong() * 4 - 2 // #entris is 2 bytes, we read 4 bytes, so we subtract 2
0x03.toByte() -> 4 // symlink
else -> throw InternalError("Unknown entry type: ${entryType.toUint()}")
}
skipRead(entrySize) // skips rest of the entry's actual contents
}
}
private fun ByteArray.toIntBig(): Int {
return this[0].toUint().shl(24) or this[1].toUint().shl(16) or
this[2].toUint().shl(8) or this[2].toUint()
}
private fun ByteArray.toInt48(): Long {
return this[0].toUlong().shl(56) or this[1].toUlong().shl(48) or
this[2].toUlong().shl(40) or this[3].toUlong().shl(32) or
this[4].toUlong().shl(24) or this[5].toUlong().shl(16) or
this[6].toUlong().shl(8) or this[7].toUlong()
}
}

View File

@@ -4,6 +4,10 @@ import java.io.*
import java.nio.charset.Charset
import java.util.*
import java.util.logging.Level
import java.util.zip.DeflaterOutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.zip.InflaterOutputStream
import javax.naming.OperationNotSupportedException
import kotlin.collections.ArrayList
@@ -41,7 +45,7 @@ object VDUtil {
unsanitisedHierarchy.removeAt(0)
// removes tail slash
if (unsanitisedHierarchy.size > 0 &&
unsanitisedHierarchy[unsanitisedHierarchy.lastIndex].isEmpty())
unsanitisedHierarchy[unsanitisedHierarchy.lastIndex].isEmpty())
unsanitisedHierarchy.removeAt(unsanitisedHierarchy.lastIndex)
unsanitisedHierarchy.forEach {
@@ -116,12 +120,12 @@ object VDUtil {
if (magicMismatch(VirtualDisk.MAGIC, inbytes.sliceArray(0L..3L).toByteArray()))
if (magicMismatch(VirtualDisk.MAGIC, inbytes.sliceArray64(0L..3L).toByteArray()))
throw RuntimeException("Invalid Virtual Disk file!")
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 diskSize = inbytes.sliceArray64(4L..9L).toInt48Big()
val diskName = inbytes.sliceArray64(10L..10L + 31)
val diskCRC = inbytes.sliceArray64(10L + 32..10L + 32 + 3).toIntBig() // to check with completed vdisk
val diskSpecVersion = inbytes[10L + 32 + 4]
@@ -133,30 +137,31 @@ object VDUtil {
//println("[VDUtil] currentUnixtime = $currentUnixtime")
var entryOffset = VirtualDisk.HEADER_SIZE
while (!Arrays.equals(inbytes.sliceArray(entryOffset..entryOffset + 3).toByteArray(), VirtualDisk.FOOTER_START_MARK)) {
// not footer, entries
while (!Arrays.equals(inbytes.sliceArray64(entryOffset..entryOffset + 3).toByteArray(), VirtualDisk.FOOTER_START_MARK)) {
//println("[VDUtil] entryOffset = $entryOffset")
// read and prepare all the shits
val entryID = inbytes.sliceArray(entryOffset..entryOffset + 3).toIntBig()
val entryParentID = inbytes.sliceArray(entryOffset + 4..entryOffset + 7).toIntBig()
val entryID = inbytes.sliceArray64(entryOffset..entryOffset + 3).toIntBig()
val entryParentID = inbytes.sliceArray64(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 entryFileName = inbytes.sliceArray64(entryOffset + 9..entryOffset + 9 + 255).toByteArray()
val entryCreationTime = inbytes.sliceArray64(entryOffset + 265..entryOffset + 270).toInt48Big()
val entryModifyTime = inbytes.sliceArray64(entryOffset + 271..entryOffset + 276).toInt48Big()
val entryCRC = inbytes.sliceArray64(entryOffset + 277..entryOffset + 280).toIntBig() // to check with completed entry
val entryData = when (entryTypeFlag) {
DiskEntry.NORMAL_FILE -> {
val filesize = inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 5).toInt48Big()
val filesize = inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 5).toInt48Big()
//println("[VDUtil] --> is file; filesize = $filesize")
inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE + 6..entryOffset + DiskEntry.HEADER_SIZE + 5 + filesize)
inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE + 6..entryOffset + DiskEntry.HEADER_SIZE + 5 + filesize)
}
DiskEntry.DIRECTORY -> {
val entryCount = inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 1).toShortBig()
val entryCount = inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 1).toShortBig()
//println("[VDUtil] --> is directory; entryCount = $entryCount")
inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE + 2..entryOffset + DiskEntry.HEADER_SIZE + 1 + entryCount * 4)
inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE + 2..entryOffset + DiskEntry.HEADER_SIZE + 1 + entryCount * 4)
}
DiskEntry.SYMLINK -> {
inbytes.sliceArray(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 3)
inbytes.sliceArray64(entryOffset + DiskEntry.HEADER_SIZE..entryOffset + DiskEntry.HEADER_SIZE + 3)
}
else -> throw RuntimeException("Unknown entry with type $entryTypeFlag at entryOffset $entryOffset")
}
@@ -165,9 +170,10 @@ object VDUtil {
// update entryOffset so that we can fetch next entry in the binary
entryOffset += DiskEntry.HEADER_SIZE + entryData.size + when (entryTypeFlag) {
DiskEntry.NORMAL_FILE -> 6
DiskEntry.DIRECTORY -> 2
DiskEntry.SYMLINK -> 0
DiskEntry.COMPRESSED_FILE -> 12 // PLEASE DO REFER TO Spec.md
DiskEntry.NORMAL_FILE -> 6 // PLEASE DO REFER TO Spec.md
DiskEntry.DIRECTORY -> 2 // PLEASE DO REFER TO Spec.md
DiskEntry.SYMLINK -> 0 // PLEASE DO REFER TO Spec.md
else -> throw RuntimeException("Unknown entry with type $entryTypeFlag")
}
@@ -185,7 +191,7 @@ object VDUtil {
else if (entryTypeFlag == DiskEntry.DIRECTORY) {
val entryList = ArrayList<EntryID>()
(0..entryData.size / 4 - 1).forEach {
entryList.add(entryData.sliceArray(4 * it..4 * it + 3).toIntBig())
entryList.add(entryData.sliceArray64(4 * it..4 * it + 3).toIntBig())
}
EntryDirectory(entryList)
@@ -202,7 +208,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)
@@ -215,6 +221,15 @@ object VDUtil {
// add entry to disk
vdisk.entries[entryID] = diskEntry
}
// entries ends, footers are to be read
run {
entryOffset += 4 // skip footer marker
val footerSize = inbytes.size - entryOffset - VirtualDisk.EOF_MARK.size
if (footerSize > 0) {
vdisk.__internalSetFooter__(inbytes.sliceArray64(entryOffset..entryOffset + footerSize - 1))
}
}
// check CRC of disk
@@ -235,6 +250,11 @@ object VDUtil {
}
fun isFile(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntryFile
fun isCompressedFile(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntryFileCompressed
fun isDirectory(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntryDirectory
fun isSymlink(disk: VirtualDisk, entryID: EntryID) = disk.entries[entryID]?.contents is EntrySymlink
/**
* Get list of entries of directory.
*/
@@ -317,12 +337,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)
*
@@ -330,12 +350,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.
@@ -441,14 +461,28 @@ object VDUtil {
* Add file to the specified directory.
* The file will get new EntryID and its ParentID will be overwritten.
*/
fun addFile(disk: VirtualDisk, parentPath: VDPath, file: DiskEntry) {
fun addFile(disk: VirtualDisk, parentPath: VDPath, file: DiskEntry, compress: Boolean = false) {
val targetDirID = getFile(disk, parentPath)!!.entryID
return addFile(disk, targetDirID, file)
return addFile(disk, targetDirID, file, compress)
}
fun randomBase62(length: Int): String {
val glyphs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
val sb = StringBuilder()
kotlin.repeat(length) {
sb.append(glyphs[(Math.random() * glyphs.length).toInt()])
}
return sb.toString()
}
/**
* Add file to the specified directory. ParentID of the file will be overwritten.
*
* @param compressTheFile Used to compress un-compressed file. Will be ignored if the entry is Dir, Symlink or EntryFileCompressed
*/
fun addFile(disk: VirtualDisk, directoryID: EntryID, file: DiskEntry) {
fun addFile(disk: VirtualDisk, directoryID: EntryID, file: DiskEntry, compressTheFile: Boolean = false) {
disk.checkReadOnly()
disk.checkCapacity(file.serialisedSize)
@@ -457,8 +491,53 @@ object VDUtil {
file.entryID = disk.generateUniqueID()
// add record to the directory
getAsDirectory(disk, directoryID).add(file.entryID)
// add entry on the disk
disk.entries[file.entryID] = file
// DEFLATE fat boy if marked as
if (compressTheFile && file.contents is EntryFile) {
val filename = "./tmp_" + randomBase62(10)
// dump the deflated bytes to disk
file.contents.bytes.forEachBanks {
val fos = BufferedOutputStream(FileOutputStream(filename))
val deflater = DeflaterOutputStream(fos, true)
deflater.write(it)
deflater.flush()
deflater.close()
}
// read back deflated bytes untouched and store it
val tempFile = File(filename)
val tempFileFIS = FileInputStream(tempFile)
val readBytes = ByteArray64(tempFile.length())
var c = 0L
while (true) {
val r = tempFileFIS.read()
if (r == -1) break
else readBytes[c] = r.toByte()
c++
}
tempFileFIS.close()
val newContent = EntryFileCompressed(file.contents.bytes.size, readBytes)
val newEntry = DiskEntry(
file.entryID, file.parentEntryID, file.filename, file.creationDate, file.modificationDate,
newContent
)
disk.entries[file.entryID] = newEntry
}
// just the add the boy to the house
else {
disk.entries[file.entryID] = file
}
// make this boy recognise his new parent
file.parentEntryID = directoryID
}
@@ -599,7 +678,20 @@ object VDUtil {
*/
fun exportFile(entryFile: EntryFile, outfile: File) {
outfile.createNewFile()
outfile.writeBytes64(entryFile.bytes)
if (entryFile is EntryFileCompressed) {
entryFile.bytes.forEachBanks {
val fos = FileOutputStream(outfile)
val inflater = InflaterOutputStream(fos)
inflater.write(it)
inflater.flush()
inflater.close()
}
}
else {
outfile.writeBytes64(entryFile.bytes)
}
}
fun exportDirRecurse(disk: VirtualDisk, parentDir: EntryID, outfile: File, charset: Charset) {
@@ -851,6 +943,7 @@ object VDUtil {
}
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean {
return !Arrays.equals(array, magic)
}

View File

@@ -3,8 +3,9 @@ package net.torvald.terrarum.virtualcomputer.tvd
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
import java.util.function.Consumer
import java.util.zip.CRC32
import kotlin.experimental.and
import kotlin.experimental.or
/**
* Created by minjaesong on 2017-03-31.
@@ -12,23 +13,33 @@ import java.util.zip.CRC32
typealias EntryID = Int
val specversion = 0x02.toByte()
val specversion = 0x03.toByte()
class VirtualDisk(
/** capacity of 0 makes the disk read-only */
var capacity: Long,
var diskName: ByteArray = ByteArray(NAME_LENGTH)
var diskName: ByteArray = ByteArray(NAME_LENGTH),
footer: ByteArray64 = ByteArray64(8) // default to mandatory 8-byte footer
) {
var footerBytes: ByteArray64 = footer
private set
val entries = HashMap<EntryID, DiskEntry>()
val isReadOnly: Boolean
get() = capacity == 0L
var isReadOnly: Boolean
set(value) { footerBytes[0] = (footerBytes[0] and 0xFE.toByte()) or value.toBit() }
get() = capacity == 0L || (footerBytes.size > 0 && footerBytes[0].and(1) == 1.toByte())
fun getDiskNameString(charset: Charset) = String(diskName, charset)
val root: DiskEntry
get() = entries[0]!!
private fun Boolean.toBit() = if (this) 1.toByte() else 0.toByte()
internal fun __internalSetFooter__(footer: ByteArray64) {
footerBytes = footer
}
private fun serializeEntriesOnly(): ByteArray64 {
val bufferList = ArrayList<Byte>()
val bufferList = ArrayList<Byte>() // FIXME this part would take up excessive memory for large files
entries.forEach {
val serialised = it.value.serialize()
serialised.forEach { bufferList.add(it) }
@@ -41,7 +52,7 @@ class VirtualDisk(
fun serialize(): AppendableByteBuffer {
val entriesBuffer = serializeEntriesOnly()
val buffer = AppendableByteBuffer(HEADER_SIZE + entriesBuffer.size + FOOTER_SIZE)
val buffer = AppendableByteBuffer(HEADER_SIZE + entriesBuffer.size + FOOTER_SIZE + footerBytes.size)
val crc = hashCode().toBigEndian()
buffer.put(MAGIC)
@@ -51,6 +62,7 @@ class VirtualDisk(
buffer.put(specversion)
buffer.put(entriesBuffer)
buffer.put(FOOTER_START_MARK)
buffer.put(footerBytes)
buffer.put(EOF_MARK)
return buffer
@@ -122,6 +134,8 @@ class DiskEntry(
val NORMAL_FILE = 1.toByte()
val DIRECTORY = 2.toByte()
val SYMLINK = 3.toByte()
val COMPRESSED_FILE = 0x11.toByte()
private fun DiskEntryContent.getTypeFlag() =
if (this is EntryFile) NORMAL_FILE
else if (this is EntryDirectory) DIRECTORY
@@ -168,7 +182,12 @@ interface DiskEntryContent {
fun getSizePure(): Long
fun getSizeEntry(): Long
}
class EntryFile(var bytes: ByteArray64) : DiskEntryContent {
/**
* Do not retrieve bytes directly from this! Use VDUtil.retrieveFile(DiskEntry)
* And besides, the bytes could be compressed.
*/
open class EntryFile(internal var bytes: ByteArray64) : DiskEntryContent {
override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 6
@@ -183,6 +202,21 @@ class EntryFile(var bytes: ByteArray64) : DiskEntryContent {
return buffer
}
}
class EntryFileCompressed(internal var uncompressedSize: Long, bytes: ByteArray64) : EntryFile(bytes) {
override fun getSizePure() = bytes.size
override fun getSizeEntry() = getSizePure() + 12
/* No new blank file for the compressed */
override fun serialize(): AppendableByteBuffer {
val buffer = AppendableByteBuffer(getSizeEntry())
buffer.put(getSizePure().toInt48())
buffer.put(uncompressedSize.toInt48())
buffer.put(bytes)
return buffer
}
}
class EntryDirectory(private val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
override fun getSizePure() = entries.size * 4L

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.