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 000000000..88cae4a67 Binary files /dev/null and b/src/net/torvald/terrarum/virtualcomputer/tvd/tvd_20170723.zip differ 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 000000000..38af02de1 Binary files /dev/null and b/work_files/alto_startup_mock.ans differ diff --git a/work_files/computer_bios_mockup.ans b/work_files/computer_bios_mockup.ans new file mode 100644 index 000000000..b7d62f7d0 Binary files /dev/null and b/work_files/computer_bios_mockup.ans differ diff --git a/work_files/computer_mockup_template.ans b/work_files/computer_mockup_template.ans new file mode 100644 index 000000000..8a6b2157c Binary files /dev/null and b/work_files/computer_mockup_template.ans differ 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 000000000..d12d5efaf Binary files /dev/null and b/work_files/graphics/fonts/telegraph/bcd_encoding_map.bin.bak differ 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 000000000..dcda455d9 Binary files /dev/null and b/work_files/graphics/gui/watches/poketch_prg01_graphics.bin differ 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