mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +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,
|
"speed": 3.0,
|
||||||
"speedmult": [100,100,100,100,100,100,100],
|
"speedmult": [100,100,100,100,100,100,100],
|
||||||
|
|
||||||
"jumppower": 4.3,
|
"jumppower": 13.0,
|
||||||
"jumppowermult": [100,100,100,100,100,100,100],
|
"jumppowermult": [100,100,100,100,100,100,100],
|
||||||
|
|
||||||
"scale": 1.0,
|
"scale": 1.0,
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
"id";"filename"
|
"id";"classname"
|
||||||
"8448";"testpick.groovy"
|
"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.GameItem
|
||||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||||
|
import net.torvald.terrarum.itemproperties.ItemID
|
||||||
import net.torvald.terrarum.langpack.Lang
|
import net.torvald.terrarum.langpack.Lang
|
||||||
import net.torvald.terrarum.utils.JsonFetcher
|
import net.torvald.terrarum.utils.JsonFetcher
|
||||||
import org.apache.commons.csv.CSVFormat
|
import org.apache.commons.csv.CSVFormat
|
||||||
@@ -182,12 +183,14 @@ object ModMgr {
|
|||||||
@JvmStatic operator fun invoke(module: String) {
|
@JvmStatic operator fun invoke(module: String) {
|
||||||
val csv = CSVFetcher.readFromModule(module, itemPath + "itemid.csv")
|
val csv = CSVFetcher.readFromModule(module, itemPath + "itemid.csv")
|
||||||
csv.forEach {
|
csv.forEach {
|
||||||
val filename = it["filename"].toString()
|
val className = it["classname"].toString()
|
||||||
val script = getFile(module, itemPath + filename).readText()
|
|
||||||
val itemID = it["id"].toInt()
|
val itemID = it["id"].toInt()
|
||||||
|
|
||||||
groovyEngine.eval(script)
|
val loadedClass = Class.forName(className)
|
||||||
ItemCodex[itemID] = groovyInvocable.invokeFunction("invoke", itemID) as GameItem
|
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 */
|
/** how long the walk button has down, in frames */
|
||||||
internal var walkCounterX = 0
|
internal var walkCounterX = 0
|
||||||
internal var walkCounterY = 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_totalX = 0.0
|
||||||
private var readonly_totalY = 0.0
|
private var readonly_totalY = 0.0
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ object PlayerBuilderSigrid {
|
|||||||
p.actorValue[AVKey.SPEEDBUFF] = 1.0
|
p.actorValue[AVKey.SPEEDBUFF] = 1.0
|
||||||
p.actorValue[AVKey.ACCEL] = ActorHumanoid.WALK_ACCEL_BASE
|
p.actorValue[AVKey.ACCEL] = ActorHumanoid.WALK_ACCEL_BASE
|
||||||
p.actorValue[AVKey.ACCELBUFF] = 1.0
|
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.BASEMASS] = 80.0
|
||||||
p.actorValue[AVKey.SCALEBUFF] = 1.0 // Constant 1.0 for player, meant to be used by random mobs
|
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
|
package net.torvald.terrarum.virtualcomputer.tvd
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
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.
|
* Works kind of like Bank Switching of old game console's cartridges which does same thing.
|
||||||
*
|
*
|
||||||
* Created by Minjaesong on 2017-04-12.
|
* Created by Minjaesong on 2017-04-12.
|
||||||
*/
|
*/
|
||||||
class ByteArray64(val size: Long) {
|
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>
|
private val data: Array<ByteArray>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (size <= 0)
|
if (size < 0)
|
||||||
throw IllegalArgumentException("Invalid array size!")
|
throw IllegalArgumentException("Invalid array size!")
|
||||||
|
|
||||||
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
|
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 forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
|
||||||
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().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)
|
val newarr = ByteArray64(range.last - range.first + 1)
|
||||||
range.forEach { index ->
|
range.forEach { index ->
|
||||||
newarr[index - range.first] = this[index]
|
newarr[index - range.first] = this[index]
|
||||||
@@ -103,6 +109,14 @@ class ByteArray64(val size: Long) {
|
|||||||
return newarr
|
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 {
|
fun toByteArray(): ByteArray {
|
||||||
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
|
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
|
||||||
throw TypeCastException("Impossible cast; too large to fit")
|
throw TypeCastException("Impossible cast; too large to fit")
|
||||||
@@ -126,3 +140,18 @@ class ByteArray64(val size: Long) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.nio.charset.Charset
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.Level
|
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 javax.naming.OperationNotSupportedException
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@@ -41,7 +45,7 @@ object VDUtil {
|
|||||||
unsanitisedHierarchy.removeAt(0)
|
unsanitisedHierarchy.removeAt(0)
|
||||||
// removes tail slash
|
// removes tail slash
|
||||||
if (unsanitisedHierarchy.size > 0 &&
|
if (unsanitisedHierarchy.size > 0 &&
|
||||||
unsanitisedHierarchy[unsanitisedHierarchy.lastIndex].isEmpty())
|
unsanitisedHierarchy[unsanitisedHierarchy.lastIndex].isEmpty())
|
||||||
unsanitisedHierarchy.removeAt(unsanitisedHierarchy.lastIndex)
|
unsanitisedHierarchy.removeAt(unsanitisedHierarchy.lastIndex)
|
||||||
|
|
||||||
unsanitisedHierarchy.forEach {
|
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!")
|
throw RuntimeException("Invalid Virtual Disk file!")
|
||||||
|
|
||||||
val diskSize = inbytes.sliceArray(4L..9L).toInt48Big()
|
val diskSize = inbytes.sliceArray64(4L..9L).toInt48Big()
|
||||||
val diskName = inbytes.sliceArray(10L..10L + 31)
|
val diskName = inbytes.sliceArray64(10L..10L + 31)
|
||||||
val diskCRC = inbytes.sliceArray(10L + 32..10L + 32 + 3).toIntBig() // to check with completed vdisk
|
val diskCRC = inbytes.sliceArray64(10L + 32..10L + 32 + 3).toIntBig() // to check with completed vdisk
|
||||||
val diskSpecVersion = inbytes[10L + 32 + 4]
|
val diskSpecVersion = inbytes[10L + 32 + 4]
|
||||||
|
|
||||||
|
|
||||||
@@ -133,30 +137,31 @@ object VDUtil {
|
|||||||
//println("[VDUtil] currentUnixtime = $currentUnixtime")
|
//println("[VDUtil] currentUnixtime = $currentUnixtime")
|
||||||
|
|
||||||
var entryOffset = VirtualDisk.HEADER_SIZE
|
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")
|
//println("[VDUtil] entryOffset = $entryOffset")
|
||||||
// read and prepare all the shits
|
// read and prepare all the shits
|
||||||
val entryID = inbytes.sliceArray(entryOffset..entryOffset + 3).toIntBig()
|
val entryID = inbytes.sliceArray64(entryOffset..entryOffset + 3).toIntBig()
|
||||||
val entryParentID = inbytes.sliceArray(entryOffset + 4..entryOffset + 7).toIntBig()
|
val entryParentID = inbytes.sliceArray64(entryOffset + 4..entryOffset + 7).toIntBig()
|
||||||
val entryTypeFlag = inbytes[entryOffset + 8]
|
val entryTypeFlag = inbytes[entryOffset + 8]
|
||||||
val entryFileName = inbytes.sliceArray(entryOffset + 9..entryOffset + 9 + 255).toByteArray()
|
val entryFileName = inbytes.sliceArray64(entryOffset + 9..entryOffset + 9 + 255).toByteArray()
|
||||||
val entryCreationTime = inbytes.sliceArray(entryOffset + 265..entryOffset + 270).toInt48Big()
|
val entryCreationTime = inbytes.sliceArray64(entryOffset + 265..entryOffset + 270).toInt48Big()
|
||||||
val entryModifyTime = inbytes.sliceArray(entryOffset + 271..entryOffset + 276).toInt48Big()
|
val entryModifyTime = inbytes.sliceArray64(entryOffset + 271..entryOffset + 276).toInt48Big()
|
||||||
val entryCRC = inbytes.sliceArray(entryOffset + 277..entryOffset + 280).toIntBig() // to check with completed entry
|
val entryCRC = inbytes.sliceArray64(entryOffset + 277..entryOffset + 280).toIntBig() // to check with completed entry
|
||||||
|
|
||||||
val entryData = when (entryTypeFlag) {
|
val entryData = when (entryTypeFlag) {
|
||||||
DiskEntry.NORMAL_FILE -> {
|
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")
|
//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 -> {
|
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")
|
//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 -> {
|
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")
|
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
|
// update entryOffset so that we can fetch next entry in the binary
|
||||||
entryOffset += DiskEntry.HEADER_SIZE + entryData.size + when (entryTypeFlag) {
|
entryOffset += DiskEntry.HEADER_SIZE + entryData.size + when (entryTypeFlag) {
|
||||||
DiskEntry.NORMAL_FILE -> 6
|
DiskEntry.COMPRESSED_FILE -> 12 // PLEASE DO REFER TO Spec.md
|
||||||
DiskEntry.DIRECTORY -> 2
|
DiskEntry.NORMAL_FILE -> 6 // PLEASE DO REFER TO Spec.md
|
||||||
DiskEntry.SYMLINK -> 0
|
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")
|
else -> throw RuntimeException("Unknown entry with type $entryTypeFlag")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +191,7 @@ object VDUtil {
|
|||||||
else if (entryTypeFlag == DiskEntry.DIRECTORY) {
|
else if (entryTypeFlag == DiskEntry.DIRECTORY) {
|
||||||
val entryList = ArrayList<EntryID>()
|
val entryList = ArrayList<EntryID>()
|
||||||
(0..entryData.size / 4 - 1).forEach {
|
(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)
|
EntryDirectory(entryList)
|
||||||
@@ -202,7 +208,7 @@ object VDUtil {
|
|||||||
val calculatedCRC = diskEntry.hashCode()
|
val calculatedCRC = diskEntry.hashCode()
|
||||||
|
|
||||||
val crcMsg = "CRC failed: expected ${entryCRC.toHex()}, got ${calculatedCRC.toHex()}\n" +
|
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 (calculatedCRC != entryCRC) {
|
||||||
if (crcWarnLevel == Level.SEVERE)
|
if (crcWarnLevel == Level.SEVERE)
|
||||||
@@ -215,6 +221,15 @@ object VDUtil {
|
|||||||
// add entry to disk
|
// add entry to disk
|
||||||
vdisk.entries[entryID] = diskEntry
|
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
|
// 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.
|
* Get list of entries of directory.
|
||||||
*/
|
*/
|
||||||
@@ -317,12 +337,12 @@ object VDUtil {
|
|||||||
*/
|
*/
|
||||||
private fun DiskEntry.getAsNormalFile(disk: VirtualDisk): EntryFile =
|
private fun DiskEntry.getAsNormalFile(disk: VirtualDisk): EntryFile =
|
||||||
this.contents as? EntryFile ?:
|
this.contents as? EntryFile ?:
|
||||||
if (this.contents is EntryDirectory)
|
if (this.contents is EntryDirectory)
|
||||||
throw RuntimeException("this is directory")
|
throw RuntimeException("this is directory")
|
||||||
else if (this.contents is EntrySymlink)
|
else if (this.contents is EntrySymlink)
|
||||||
disk.entries[this.contents.target]!!.getAsNormalFile(disk)
|
disk.entries[this.contents.target]!!.getAsNormalFile(disk)
|
||||||
else
|
else
|
||||||
throw RuntimeException("Unknown entry type")
|
throw RuntimeException("Unknown entry type")
|
||||||
/**
|
/**
|
||||||
* SYNOPSIS disk.getFile("bin/msh.lua")!!.first.getAsNormalFile(disk)
|
* SYNOPSIS disk.getFile("bin/msh.lua")!!.first.getAsNormalFile(disk)
|
||||||
*
|
*
|
||||||
@@ -330,12 +350,12 @@ object VDUtil {
|
|||||||
*/
|
*/
|
||||||
private fun DiskEntry.getAsDirectory(disk: VirtualDisk): EntryDirectory =
|
private fun DiskEntry.getAsDirectory(disk: VirtualDisk): EntryDirectory =
|
||||||
this.contents as? EntryDirectory ?:
|
this.contents as? EntryDirectory ?:
|
||||||
if (this.contents is EntrySymlink)
|
if (this.contents is EntrySymlink)
|
||||||
disk.entries[this.contents.target]!!.getAsDirectory(disk)
|
disk.entries[this.contents.target]!!.getAsDirectory(disk)
|
||||||
else if (this.contents is EntryFile)
|
else if (this.contents is EntryFile)
|
||||||
throw RuntimeException("this is not directory")
|
throw RuntimeException("this is not directory")
|
||||||
else
|
else
|
||||||
throw RuntimeException("Unknown entry type")
|
throw RuntimeException("Unknown entry type")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for the file and returns a instance of normal file.
|
* Search for the file and returns a instance of normal file.
|
||||||
@@ -441,14 +461,28 @@ object VDUtil {
|
|||||||
* Add file to the specified directory.
|
* Add file to the specified directory.
|
||||||
* The file will get new EntryID and its ParentID will be overwritten.
|
* 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
|
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.
|
* 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.checkReadOnly()
|
||||||
disk.checkCapacity(file.serialisedSize)
|
disk.checkCapacity(file.serialisedSize)
|
||||||
|
|
||||||
@@ -457,8 +491,53 @@ object VDUtil {
|
|||||||
file.entryID = disk.generateUniqueID()
|
file.entryID = disk.generateUniqueID()
|
||||||
// add record to the directory
|
// add record to the directory
|
||||||
getAsDirectory(disk, directoryID).add(file.entryID)
|
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
|
// make this boy recognise his new parent
|
||||||
file.parentEntryID = directoryID
|
file.parentEntryID = directoryID
|
||||||
}
|
}
|
||||||
@@ -599,7 +678,20 @@ object VDUtil {
|
|||||||
*/
|
*/
|
||||||
fun exportFile(entryFile: EntryFile, outfile: File) {
|
fun exportFile(entryFile: EntryFile, outfile: File) {
|
||||||
outfile.createNewFile()
|
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) {
|
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.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||||
|
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
||||||
fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean {
|
fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean {
|
||||||
return !Arrays.equals(array, magic)
|
return !Arrays.equals(array, magic)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package net.torvald.terrarum.virtualcomputer.tvd
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Consumer
|
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
|
import kotlin.experimental.and
|
||||||
|
import kotlin.experimental.or
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2017-03-31.
|
* Created by minjaesong on 2017-03-31.
|
||||||
@@ -12,23 +13,33 @@ import java.util.zip.CRC32
|
|||||||
|
|
||||||
typealias EntryID = Int
|
typealias EntryID = Int
|
||||||
|
|
||||||
val specversion = 0x02.toByte()
|
val specversion = 0x03.toByte()
|
||||||
|
|
||||||
class VirtualDisk(
|
class VirtualDisk(
|
||||||
/** capacity of 0 makes the disk read-only */
|
/** capacity of 0 makes the disk read-only */
|
||||||
var capacity: Long,
|
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 entries = HashMap<EntryID, DiskEntry>()
|
||||||
val isReadOnly: Boolean
|
var isReadOnly: Boolean
|
||||||
get() = capacity == 0L
|
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)
|
fun getDiskNameString(charset: Charset) = String(diskName, charset)
|
||||||
val root: DiskEntry
|
val root: DiskEntry
|
||||||
get() = entries[0]!!
|
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 {
|
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 {
|
entries.forEach {
|
||||||
val serialised = it.value.serialize()
|
val serialised = it.value.serialize()
|
||||||
serialised.forEach { bufferList.add(it) }
|
serialised.forEach { bufferList.add(it) }
|
||||||
@@ -41,7 +52,7 @@ class VirtualDisk(
|
|||||||
|
|
||||||
fun serialize(): AppendableByteBuffer {
|
fun serialize(): AppendableByteBuffer {
|
||||||
val entriesBuffer = serializeEntriesOnly()
|
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()
|
val crc = hashCode().toBigEndian()
|
||||||
|
|
||||||
buffer.put(MAGIC)
|
buffer.put(MAGIC)
|
||||||
@@ -51,6 +62,7 @@ class VirtualDisk(
|
|||||||
buffer.put(specversion)
|
buffer.put(specversion)
|
||||||
buffer.put(entriesBuffer)
|
buffer.put(entriesBuffer)
|
||||||
buffer.put(FOOTER_START_MARK)
|
buffer.put(FOOTER_START_MARK)
|
||||||
|
buffer.put(footerBytes)
|
||||||
buffer.put(EOF_MARK)
|
buffer.put(EOF_MARK)
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
@@ -122,6 +134,8 @@ class DiskEntry(
|
|||||||
val NORMAL_FILE = 1.toByte()
|
val NORMAL_FILE = 1.toByte()
|
||||||
val DIRECTORY = 2.toByte()
|
val DIRECTORY = 2.toByte()
|
||||||
val SYMLINK = 3.toByte()
|
val SYMLINK = 3.toByte()
|
||||||
|
val COMPRESSED_FILE = 0x11.toByte()
|
||||||
|
|
||||||
private fun DiskEntryContent.getTypeFlag() =
|
private fun DiskEntryContent.getTypeFlag() =
|
||||||
if (this is EntryFile) NORMAL_FILE
|
if (this is EntryFile) NORMAL_FILE
|
||||||
else if (this is EntryDirectory) DIRECTORY
|
else if (this is EntryDirectory) DIRECTORY
|
||||||
@@ -168,7 +182,12 @@ interface DiskEntryContent {
|
|||||||
fun getSizePure(): Long
|
fun getSizePure(): Long
|
||||||
fun getSizeEntry(): 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 getSizePure() = bytes.size
|
||||||
override fun getSizeEntry() = getSizePure() + 6
|
override fun getSizeEntry() = getSizePure() + 6
|
||||||
@@ -183,6 +202,21 @@ class EntryFile(var bytes: ByteArray64) : DiskEntryContent {
|
|||||||
return buffer
|
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 {
|
class EntryDirectory(private val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
|
||||||
|
|
||||||
override fun getSizePure() = entries.size * 4L
|
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