From 4664c9ba0dd4038b3c0931ee6f452717f21f9baf Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 9 May 2018 05:34:39 +0900 Subject: [PATCH] ModMgr: I can load class by name; dropped Groovy script support, coding must go to JAR --- .../basegame/creatures/CreatureHuman.json | 2 +- assets/modules/basegame/items/itemid.csv | 4 +- .../items/{testpick.groovy => testpick.nope} | 0 assets/modules/dwarventech/fonts/milkymda.tga | 4 +- src/net/torvald/terrarum/ModMgr.kt | 11 +- .../terrarum/audio/SpatialAudioMixer.kt | 40 ++++ .../terrarum/gameactors/ActorHumanoid.kt | 2 +- .../gameactors/PlayerBuilderSigrid.kt | 2 +- .../virtualcomputer/tvd/ByteArray64.kt | 37 +++- .../virtualcomputer/tvd/DiskSkimmer.kt | 102 +++++++++++ .../terrarum/virtualcomputer/tvd/VDUtil.kt | 173 ++++++++++++++---- .../virtualcomputer/tvd/VirtualDisk.kt | 50 ++++- .../virtualcomputer/tvd/tvd_20170723.zip | Bin 0 -> 11425 bytes work_files/UI/inventory_nouveau_2.psd | 4 +- work_files/alto_startup_mock.ans | Bin 0 -> 5609 bytes work_files/computer_bios_mockup.ans | Bin 0 -> 10343 bytes work_files/computer_mockup_template.ans | Bin 0 -> 818 bytes .../fonts/telegraph/bcd_encoding_map.bin.bak | Bin 0 -> 640 bytes .../fonts/terrarumsansbitmap_ascii_small.psd | 4 +- .../graphics/gui/setting_audio_channels.psd | 3 + .../graphics/gui/watches/poketch_fonts.psd | 3 + .../gui/watches/poketch_prg01_graphics.bin | Bin 0 -> 16384 bytes .../gui/watches/poketch_program_01.psd | 3 + work_files/graphics/items/items24.psd | 4 +- .../sprites/fixtures/computer_parts.psd | 4 +- .../graphics/terrain/wire_single_items.psd | 4 +- 26 files changed, 383 insertions(+), 73 deletions(-) rename assets/modules/basegame/items/{testpick.groovy => testpick.nope} (100%) create mode 100644 src/net/torvald/terrarum/audio/SpatialAudioMixer.kt create mode 100644 src/net/torvald/terrarum/virtualcomputer/tvd/DiskSkimmer.kt create mode 100644 src/net/torvald/terrarum/virtualcomputer/tvd/tvd_20170723.zip create mode 100644 work_files/alto_startup_mock.ans create mode 100644 work_files/computer_bios_mockup.ans create mode 100644 work_files/computer_mockup_template.ans create mode 100644 work_files/graphics/fonts/telegraph/bcd_encoding_map.bin.bak create mode 100644 work_files/graphics/gui/setting_audio_channels.psd create mode 100644 work_files/graphics/gui/watches/poketch_fonts.psd create mode 100644 work_files/graphics/gui/watches/poketch_prg01_graphics.bin create mode 100644 work_files/graphics/gui/watches/poketch_program_01.psd diff --git a/assets/modules/basegame/creatures/CreatureHuman.json b/assets/modules/basegame/creatures/CreatureHuman.json index c1b3a039e..c266326f5 100644 --- a/assets/modules/basegame/creatures/CreatureHuman.json +++ b/assets/modules/basegame/creatures/CreatureHuman.json @@ -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, diff --git a/assets/modules/basegame/items/itemid.csv b/assets/modules/basegame/items/itemid.csv index 573a15943..19f2e3b42 100644 --- a/assets/modules/basegame/items/itemid.csv +++ b/assets/modules/basegame/items/itemid.csv @@ -1,2 +1,2 @@ - "id";"filename" - "8448";"testpick.groovy" \ No newline at end of file + "id";"classname" + "8448";"net.torvald.terrarum.modulebasegame.items.PickaxeGeneric" \ No newline at end of file diff --git a/assets/modules/basegame/items/testpick.groovy b/assets/modules/basegame/items/testpick.nope similarity index 100% rename from assets/modules/basegame/items/testpick.groovy rename to assets/modules/basegame/items/testpick.nope diff --git a/assets/modules/dwarventech/fonts/milkymda.tga b/assets/modules/dwarventech/fonts/milkymda.tga index 210149e05..dc7e9c826 100644 --- a/assets/modules/dwarventech/fonts/milkymda.tga +++ b/assets/modules/dwarventech/fonts/milkymda.tga @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c67d676417034a5c3d639fc8c6fe02335798ef44cff91e1b7190d78391d045d -size 129042 +oid sha256:fea9c5c72ee6c279133be599de50eebe6e71b08903870617e3c33551e0eb93bb +size 129068 diff --git a/src/net/torvald/terrarum/ModMgr.kt b/src/net/torvald/terrarum/ModMgr.kt index 438677086..d020a777b 100644 --- a/src/net/torvald/terrarum/ModMgr.kt +++ b/src/net/torvald/terrarum/ModMgr.kt @@ -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 } } } diff --git a/src/net/torvald/terrarum/audio/SpatialAudioMixer.kt b/src/net/torvald/terrarum/audio/SpatialAudioMixer.kt new file mode 100644 index 000000000..d78c8423c --- /dev/null +++ b/src/net/torvald/terrarum/audio/SpatialAudioMixer.kt @@ -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 + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/gameactors/ActorHumanoid.kt b/src/net/torvald/terrarum/gameactors/ActorHumanoid.kt index 0f2da4e6d..74f9cc077 100644 --- a/src/net/torvald/terrarum/gameactors/ActorHumanoid.kt +++ b/src/net/torvald/terrarum/gameactors/ActorHumanoid.kt @@ -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 diff --git a/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt b/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt index d4864b8ce..26eda5b47 100644 --- a/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt +++ b/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt @@ -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 diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt index eef296a31..f1a28adf1 100644 --- a/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/ByteArray64.kt @@ -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 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 + } + } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/DiskSkimmer.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/DiskSkimmer.kt new file mode 100644 index 000000000..32191ec77 --- /dev/null +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/DiskSkimmer.kt @@ -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() + + + 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() + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt index 0ad73b6b8..4e1fdfd0d 100644 --- a/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/VDUtil.kt @@ -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() (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) } diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt b/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt index 62f586fb7..0173e50cd 100644 --- a/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt +++ b/src/net/torvald/terrarum/virtualcomputer/tvd/VirtualDisk.kt @@ -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() - 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() + val bufferList = ArrayList() // 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 = ArrayList()) : DiskEntryContent { override fun getSizePure() = entries.size * 4L diff --git a/src/net/torvald/terrarum/virtualcomputer/tvd/tvd_20170723.zip b/src/net/torvald/terrarum/virtualcomputer/tvd/tvd_20170723.zip new file mode 100644 index 0000000000000000000000000000000000000000..88cae4a675f1cc3da119efee9dee58464a9657be GIT binary patch literal 11425 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 MP!Nz`wEw#OFQE(1=Kufz literal 0 HcmV?d00001 diff --git a/work_files/UI/inventory_nouveau_2.psd b/work_files/UI/inventory_nouveau_2.psd index c4596964f..6f3c32fac 100644 --- a/work_files/UI/inventory_nouveau_2.psd +++ b/work_files/UI/inventory_nouveau_2.psd @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecc43dc29845b465af2e1e06a0fb5f4a5d054a311d56f5bbe1244a6934081ceb -size 3112067 +oid sha256:13ec47268e7c2c2ed2ea8a2846d4f8ea866d50697b4c7cca2721e8b57d4aa2fe +size 3115264 diff --git a/work_files/alto_startup_mock.ans b/work_files/alto_startup_mock.ans new file mode 100644 index 0000000000000000000000000000000000000000..38af02de17f15cd885d0f69d6b9880c8fab69eb9 GIT binary patch literal 5609 zcmeHLJ#X7E5Ovl78NE~se?f30MN0wE!A{)5K#K%0lEugnBmx=~vJFQa{JXSd%GA|c zJGDP1cSo9$4G&V#p##tG2aoUW-o4Xsa54icZ3=utXHFD1j8 z(r5=^zS?SVIK)utfWvG?GBp_h;IH}4UxPI^HCFp_n1;H|TpnUhSzRXV5lSE)D3VNO zTpAfQI#wtFmXq7Y=)2M!LIuzt`bk-1?+xq?rX|9NL?00zaI^_NPcP%}{SoB|X5`y+ z&M+g5h9|il#gLHg%3anYBFxL(2(lW$uYtl;n42K&PGq0 zpKeoPV$eMQi53-z3Pc5>0#SjeKvdvAQ2=+N^5mFayf))Hw7VSt-gLC*`ryjR?cz;V zUM%i^l*POfSF2)qx4Nqy7jrRPKUD=5Kd`Ls3&lz5`e#S0YeBuyhPvA!ZEDRW-%$}| zQ7%?%+7N}1DsLJ5eYCEMCra6(6W5vVN3^VP^Y~CKcGxL1Xc_x%cE3741e=#uqud{D zJVynh0#SjeKvW&@k|n2#oxQvs(M{~bKMsC<|Xug}K-;@_D9nS+M>Tff&k VI#Rvc9$SAh`YOhshwcf&c5LK@W?6H^ig|}{jwjd?Tb|4fBJ0FVxZ5`C@DJMl@60I#MkmMBkNBf^_ zz_D z5%ieW0D93GU^3g1CW5c!SXA194MG=o;LT=&8Y>K_IxWtr)6}t@uI*R~4%}e0ed!08 zqgUw(Vx%EO9v~9yTncDlREpRH2>_1ef#ET;#1I64{vw{P&{*{r>}_z1guxQ}kZ@0o zwu?SRuP=sIr)U7i!g4)3N8k~sM$BYB6+e?IKud(uDjEfGO2XKi&hH5M#*@TN<{^@S`IXD0JXc0rS_N7K zS_N7KS_N7Kb}E2(TybaLNNz61lW*_8#7XGR;MVJN^5-meeV&jYC9dc3H03^VBcG&k z$cYz+AvEAr!U>Nuo^b!IyhF=0ZQ}-w=>;Y}kF&~h?l`{V(dc|UA@qYKvoZ`tWH0@2 zXH-uc(4I)sYV!vIiIq%+%WS5jhm5CWJar#LhNz2)2nmzbx=f~)NgkP^AsmR*H9xWE zkq*d7kWPu81ovD_Q5~mxbv{5LQB1DlICHONoP^vD<}k$&)$^x}9vuo32HS!ylT$B5 z6lb?Vbb~SD8r?ct+pID8YHVQ$=rs%ZT)fojp+R$2uPP=WfCVUoE?Hotpcmo=of@sEi3^8}tqsN^BL z<>a6E3ZLTnal}awkt^4m-XzTSMc}hIl+VwQB`8?Ml7c@kr)Y2NWVTfO0 zL}WBig9zRQg{}vWy*W9@S3fuI%=NOs1M~){$njBWE<$DKzJStAuM3QuJmZvagN!+D z)k7Y}$-~~E3S?#uo`-xN_#9rm{XB9-zqYAe0r5Tn*5$?z%3#aZY^7D+f)+pBD!))& s{4p7RKZfVFn;V*+YMGP`s6_|=y)leeuP%&0>hk)0^qHLgJ*=qoKV4klGXMYp literal 0 HcmV?d00001 diff --git a/work_files/computer_mockup_template.ans b/work_files/computer_mockup_template.ans new file mode 100644 index 0000000000000000000000000000000000000000..8a6b2157c44c0e30d084695907b63b127c31b2b8 GIT binary patch literal 818 zcmb_ZK~BUl4BX|2#EBcd>=W#`X|u98beDpVKw4?Vsc-15ALBnfhOv`|it|QF(@bXU zvE59htSzbZ^JVyGaFup$Ce|+DPnnO)Pcua$^^Qk2Jj7_Y2|K_DLSfxYk#kQgkDyiw z&I|;3-p)+XSV}ohE13i&NCIlXkdvciiu)B6$~$&iWVgnZK;_xr$}?%yHma0# z2xHauIgUBEs5ug9q?ybIn#9fIso`i-aX<@@j@QEMT*M`o0C=sZ^&S_rx{nBJhQS18 zhC5fsiWPgk%m0rkf$Yq`=^PQ!M&pxQM=`kC=D=F}ezIF_@!4*rM1Z-UFGTdVD{99>%vJ-|F`ltXs7w literal 0 HcmV?d00001 diff --git a/work_files/graphics/fonts/telegraph/bcd_encoding_map.bin.bak b/work_files/graphics/fonts/telegraph/bcd_encoding_map.bin.bak new file mode 100644 index 0000000000000000000000000000000000000000..d12d5efafe46b684661d07555d986f008f8bee4e GIT binary patch literal 640 zcma*k$xagh6ouhGxp$4PfD)%fZJ|J+&=yCW&}S%To~WMM>w`2q#GfwZ*IQ)ImMJxMu>7Ms3c4k)dVS_jtDVe4FS67CPfdu^pPe* zKO5Lcl(np4lEWO~C~dTJj1D?UvXY0aW*s%T|11d_X`-1HS~>>pcQ0Pn zZ}8n7V2f|{ZNA-ic)joRUB2k=@x4AtwMV?hYdz{QkB`^+W-s{rJWJ3^z05;i?iF6? zVXyLnzu%Yl7y3WgNBd--?TdZ2IUBM?f1&@SWo_18*=u`aZ|$AEw}PMVe`JsCi9NMv z_S~jz#$H&#&-b6wX`RuahIC64x~)69s}k-hLQ(Lhj;miMG@z5Zs!?6jn69fu&FWCA z9_YS$)T=(FmC+?#*04r&MR`9!qyFfII+awHx|Pydozr<;&_%^bXo@DS2##y1{$Dvi Y#=QQu|8M=@{@?UdOYQ$%`Tc+BFRyF0p8x;= literal 0 HcmV?d00001 diff --git a/work_files/graphics/fonts/terrarumsansbitmap_ascii_small.psd b/work_files/graphics/fonts/terrarumsansbitmap_ascii_small.psd index 937d6ec5f..7d6867966 100644 --- a/work_files/graphics/fonts/terrarumsansbitmap_ascii_small.psd +++ b/work_files/graphics/fonts/terrarumsansbitmap_ascii_small.psd @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb4bfdb6db11de809f631c75f298d9da5bbfdfa4fe00f736ff4db60b7affe3e0 -size 31255 +oid sha256:7a2230b55c6efdd1a7d993478670418968952d9914572a321d497aee47474a9c +size 35906 diff --git a/work_files/graphics/gui/setting_audio_channels.psd b/work_files/graphics/gui/setting_audio_channels.psd new file mode 100644 index 000000000..c1fc2e387 --- /dev/null +++ b/work_files/graphics/gui/setting_audio_channels.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:329d283add664debc4b27f64b0a6900ee8f2fafc01c822f656254b93b9862657 +size 98576 diff --git a/work_files/graphics/gui/watches/poketch_fonts.psd b/work_files/graphics/gui/watches/poketch_fonts.psd new file mode 100644 index 000000000..daa9ab97b --- /dev/null +++ b/work_files/graphics/gui/watches/poketch_fonts.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:028cf0d2214cdb62241cea3fed58157c340ecc3906db22196c54789f55e935f4 +size 44808 diff --git a/work_files/graphics/gui/watches/poketch_prg01_graphics.bin b/work_files/graphics/gui/watches/poketch_prg01_graphics.bin new file mode 100644 index 0000000000000000000000000000000000000000..dcda455d97c38fec0b348063634a94eb4d5a433a GIT binary patch literal 16384 zcmeH{zi#3{5XKD?Who*SDGf!0Bk=-}+@xZ8lglF@U4wK;Ah~eKBP4IaqAODR5dum< ziIEG4V(`t{S-`6#CtY>LH;rfKv**um2lBi$@54Thxjnz9rP*_uR^JztrlC;E>O`OD zVVWLwQPqEk*8y=3tGoG$lqqr4$@xCw+Jb_>eos*~Kj&VpR&i|gIIijue@owdU+8E3 zhfaQ@8?*V{`u-2UDEQ<(6|a=l^CMq#o=&T06SMh(pev*l-P75qC*8*X_36$Z1l9Z@ zslsf23ah$s=XdUh?@x(u_QTbcs^%w(3p>_#_N(mf>;3yx-0taq-1&Y6*F@c~^}pS( znV&@Wc>ivG!%v*;kGsF_eC~dh+jZ*C-LF!o$nuGt{Wdy9U+;fOJI{5p%MJ&7GS9;y z+a)?*Z{OecS*91sZkHL`zrAnQIXk;uve2`5^}5_7$wV&}i^SMw`MQc{>~KI&)HDj` zbFIC}WTFe3&%hHz)r$_@+e6%7~Z$9>O2Ft!Sx%h+lM|onj7U=weCOMwi;f>GjJPc#(gf5 z%m$P%g&-oY)wsB)47DP{kzebZ^OBP9paSjuMSrhV7X$K$ct zQ9T%rdcAU6Ig~5a>y3s3oo$x$Fy#C-p1ZIxoG&+-aXIAe)75h{m6D-wU1QVed6n+j z;Q-b0?fT_$rW7;dvc_hQFY9g24)-NKel2-U+%uOwGw#cmWaq>a1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; bKmY_l00ck)1V8`;KmY_l00cnb{~+)O=IA8v literal 0 HcmV?d00001 diff --git a/work_files/graphics/gui/watches/poketch_program_01.psd b/work_files/graphics/gui/watches/poketch_program_01.psd new file mode 100644 index 000000000..cf1618e08 --- /dev/null +++ b/work_files/graphics/gui/watches/poketch_program_01.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33c5a1a2f34c0e7dafa9ab54ae345f172aba5d4bc87c97c112a93724c70ba57c +size 42381 diff --git a/work_files/graphics/items/items24.psd b/work_files/graphics/items/items24.psd index d0fe9085f..0ffb8951e 100755 --- a/work_files/graphics/items/items24.psd +++ b/work_files/graphics/items/items24.psd @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47afd2da34299f34c0513b94bc52f3fe5a5f62f7f59106448409640e5e3977d2 -size 956752 +oid sha256:95e8f0f98299935dc4664b3ce38c2731fdd13f6c70266c6bd4e36efd9c048ac7 +size 969490 diff --git a/work_files/graphics/sprites/fixtures/computer_parts.psd b/work_files/graphics/sprites/fixtures/computer_parts.psd index 0da57411c..cb7428346 100644 --- a/work_files/graphics/sprites/fixtures/computer_parts.psd +++ b/work_files/graphics/sprites/fixtures/computer_parts.psd @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b13c6e04e83d5ca68e29ad5d761ca277eb61633c1fd71b6b5b0caf9a8a4b19a4 -size 222344 +oid sha256:faf85655c07cc2c206fd67374ec7a0e2c27506554e28286bc17963cf3d05a1e3 +size 224648 diff --git a/work_files/graphics/terrain/wire_single_items.psd b/work_files/graphics/terrain/wire_single_items.psd index 89aa333e3..52d2449da 100644 --- a/work_files/graphics/terrain/wire_single_items.psd +++ b/work_files/graphics/terrain/wire_single_items.psd @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4afd043976153a4387e8c6c4ffc83900f794b403f4c313ad204d951f62c06a55 -size 485092 +oid sha256:511738b7e632902c5305b4a5edded4e5a752c43218feb6db9c9041ffa5d38a4e +size 489780