mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
ModMgr: I can load class by name; dropped Groovy script support, coding must go to JAR
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
"id";"filename"
|
||||
"8448";"testpick.groovy"
|
||||
"id";"classname"
|
||||
"8448";"net.torvald.terrarum.modulebasegame.items.PickaxeGeneric"
|
||||
|
Binary file not shown.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
src/net/torvald/terrarum/audio/SpatialAudioMixer.kt
Normal file
40
src/net/torvald/terrarum/audio/SpatialAudioMixer.kt
Normal 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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/net/torvald/terrarum/virtualcomputer/tvd/DiskSkimmer.kt
Normal file
102
src/net/torvald/terrarum/virtualcomputer/tvd/DiskSkimmer.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
src/net/torvald/terrarum/virtualcomputer/tvd/tvd_20170723.zip
Normal file
BIN
src/net/torvald/terrarum/virtualcomputer/tvd/tvd_20170723.zip
Normal file
Binary file not shown.
Binary file not shown.
BIN
work_files/alto_startup_mock.ans
Normal file
BIN
work_files/alto_startup_mock.ans
Normal file
Binary file not shown.
BIN
work_files/computer_bios_mockup.ans
Normal file
BIN
work_files/computer_bios_mockup.ans
Normal file
Binary file not shown.
BIN
work_files/computer_mockup_template.ans
Normal file
BIN
work_files/computer_mockup_template.ans
Normal file
Binary file not shown.
BIN
work_files/graphics/fonts/telegraph/bcd_encoding_map.bin.bak
Normal file
BIN
work_files/graphics/fonts/telegraph/bcd_encoding_map.bin.bak
Normal file
Binary file not shown.
Binary file not shown.
BIN
work_files/graphics/gui/setting_audio_channels.psd
LFS
Normal file
BIN
work_files/graphics/gui/setting_audio_channels.psd
LFS
Normal file
Binary file not shown.
BIN
work_files/graphics/gui/watches/poketch_fonts.psd
LFS
Normal file
BIN
work_files/graphics/gui/watches/poketch_fonts.psd
LFS
Normal file
Binary file not shown.
BIN
work_files/graphics/gui/watches/poketch_prg01_graphics.bin
Normal file
BIN
work_files/graphics/gui/watches/poketch_prg01_graphics.bin
Normal file
Binary file not shown.
BIN
work_files/graphics/gui/watches/poketch_program_01.psd
LFS
Normal file
BIN
work_files/graphics/gui/watches/poketch_program_01.psd
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user