diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/Assembly.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/Assembly.kt index 98591f3b..2312ebd9 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/Assembly.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/Assembly.kt @@ -177,10 +177,10 @@ private class Assembler(private val assembly: List, private val manualSt is DataSegment -> { val oldSize = seg.data.size - seg.data.size += bytes.size.toUInt() + seg.data.size += bytes.size for (i in bytes.indices) { - seg.data.setI8(i.toUInt() + oldSize, bytes[i]) + seg.data.setI8(i + oldSize, bytes[i]) } } @@ -231,7 +231,9 @@ private class Assembler(private val assembly: List, private val manualSt } private fun addUnexpectedTokenError(token: Token) { - addError(token, "Unexpected token.", "Unexpected ${token::class.simpleName} at ${token.srcLoc()}.") + addError(token, + "Unexpected token.", + "Unexpected ${token::class.simpleName} at ${token.srcLoc()}.") } private fun addWarning(token: Token, uiMessage: String) { @@ -288,7 +290,7 @@ private class Assembler(private val assembly: List, private val manualSt if (!prevLineHadLabel) { segment = DataSegment( labels = mutableListOf(label), - data = Buffer.withCapacity(0u), + data = Buffer.withCapacity(0), srcLoc = SegmentSrcLoc(labels = mutableListOf(srcLoc)), ) objectCode.add(segment!!) diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt index 1f66adf0..a7052b14 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt @@ -6,102 +6,109 @@ import world.phantasmal.lib.Endianness * Resizable, continuous block of bytes which is reallocated when necessary. */ expect class Buffer { - var size: UInt + var size: Int /** * Byte order mode. */ var endianness: Endianness - val capacity: UInt + val capacity: Int /** * Reads an unsigned 8-bit integer at the given offset. */ - fun getU8(offset: UInt): UByte + fun getU8(offset: Int): UByte /** * Reads an unsigned 16-bit integer at the given offset. */ - fun getU16(offset: UInt): UShort + fun getU16(offset: Int): UShort /** * Reads an unsigned 32-bit integer at the given offset. */ - fun getU32(offset: UInt): UInt + fun getU32(offset: Int): UInt /** * Reads a signed 8-bit integer at the given offset. */ - fun getI8(offset: UInt): Byte + fun getI8(offset: Int): Byte /** * Reads a signed 16-bit integer at the given offset. */ - fun getI16(offset: UInt): Short + fun getI16(offset: Int): Short /** * Reads a signed 32-bit integer at the given offset. */ - fun getI32(offset: UInt): Int + fun getI32(offset: Int): Int /** * Reads a 32-bit floating point number at the given offset. */ - fun getF32(offset: UInt): Float + fun getF32(offset: Int): Float /** * Reads a UTF-16-encoded string at the given offset. */ - fun getStringUtf16(offset: UInt, maxByteLength: UInt, nullTerminated: Boolean): String + fun getStringUtf16(offset: Int, maxByteLength: Int, nullTerminated: Boolean): String /** * Returns a copy of this buffer at the given offset with the given size. */ - fun slice(offset: UInt, size: UInt): Buffer + fun slice(offset: Int, size: Int): Buffer /** * Writes an unsigned 8-bit integer at the given offset. */ - fun setU8(offset: UInt, value: UByte): Buffer + fun setU8(offset: Int, value: UByte): Buffer /** * Writes an unsigned 16-bit integer at the given offset. */ - fun setU16(offset: UInt, value: UShort): Buffer + fun setU16(offset: Int, value: UShort): Buffer /** * Writes an unsigned 32-bit integer at the given offset. */ - fun setU32(offset: UInt, value: UInt): Buffer + fun setU32(offset: Int, value: UInt): Buffer /** * Writes a signed 8-bit integer at the given offset. */ - fun setI8(offset: UInt, value: Byte): Buffer + fun setI8(offset: Int, value: Byte): Buffer /** * Writes a signed 16-bit integer at the given offset. */ - fun setI16(offset: UInt, value: Short): Buffer + fun setI16(offset: Int, value: Short): Buffer /** * Writes a signed 32-bit integer at the given offset. */ - fun setI32(offset: UInt, value: Int): Buffer + fun setI32(offset: Int, value: Int): Buffer /** * Writes a 32-bit floating point number at the given offset. */ - fun setF32(offset: UInt, value: Float): Buffer + fun setF32(offset: Int, value: Float): Buffer /** * Writes 0 bytes to the entire buffer. */ fun zero(): Buffer + /** + * Writes [value] to every byte in the buffer. + */ + fun fill(value: Byte): Buffer + companion object { - fun withCapacity(initialCapacity: UInt, endianness: Endianness = Endianness.Little): Buffer + fun withCapacity(initialCapacity: Int, endianness: Endianness = Endianness.Little): Buffer + + fun withSize(initialSize: Int, endianness: Endianness = Endianness.Little): Buffer fun fromByteArray(array: ByteArray, endianness: Endianness = Endianness.Little): Buffer } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsCompress.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsCompress.kt new file mode 100644 index 00000000..49705a9f --- /dev/null +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsCompress.kt @@ -0,0 +1,138 @@ +package world.phantasmal.lib.compression.prs + +import world.phantasmal.lib.Endianness +import world.phantasmal.lib.buffer.Buffer +import world.phantasmal.lib.cursor.Cursor +import world.phantasmal.lib.cursor.WritableCursor +import world.phantasmal.lib.cursor.cursor +import kotlin.math.max +import kotlin.math.min + +fun prsCompress(cursor: Cursor): Cursor { + val compressor = PrsCompressor(cursor.size, cursor.endianness) + val comparisonCursor = cursor.take(cursor.size) + cursor.seekStart(0) + + while (cursor.hasBytesLeft()) { + // Find the longest match. + var bestOffset = 0 + var bestSize = 0 + val startPos = cursor.position + val minOffset = max(0, startPos - min(0x800, cursor.bytesLeft)) + + for (i in startPos - 255 downTo minOffset) { + comparisonCursor.seekStart(i) + var size = 0 + + while (cursor.hasBytesLeft() && size <= 254 && cursor.u8() == comparisonCursor.u8()) { + size++ + } + + cursor.seekStart(startPos) + + if (size >= bestSize) { + bestOffset = i + bestSize = size + + if (size >= 255) { + break + } + } + } + + if (bestSize < 3) { + compressor.addU8(cursor.u8()) + } else { + compressor.copy(bestOffset - cursor.position, bestSize) + cursor.seek(bestSize) + } + } + + return compressor.finalize() +} + +private class PrsCompressor(capacity: Int, endianness: Endianness) { + private val output: WritableCursor = Buffer.withCapacity(capacity, endianness).cursor() + private var flags = 0 + private var flagBitsLeft = 0 + private var flagOffset = 0 + + fun addU8(value: UByte) { + writeControlBit(1) + writeU8(value) + } + + fun copy(offset: Int, size: Int) { + if (offset > -256 && size <= 5) { + shortCopy(offset, size) + } else { + longCopy(offset, size) + } + } + + fun finalize(): Cursor { + writeControlBit(0) + writeControlBit(1) + + flags = flags ushr flagBitsLeft + val pos = output.position + output.seekStart(flagOffset).writeU8(flags.toUByte()).seekStart(pos) + + writeU8(0u) + writeU8(0u) + return output.seekStart(0) + } + + private fun writeControlBit(bit: Int) { + if (flagBitsLeft == 0) { + // Write out the flags to their position in the file, and store the next flags byte + // position. + val pos = output.position + output.seekStart(flagOffset) + output.writeU8(flags.toUByte()) + output.seekStart(pos) + output.writeU8(0u) // Placeholder for the next flags byte. + flagOffset = pos + flagBitsLeft = 8 + } + + flags = flags ushr 1 + + if (bit!=0) { + flags = flags or 0x80 + } + + flagBitsLeft-- + } + + private fun writeU8(data: UByte) { + output.writeU8(data) + } + + private fun writeU8(data: Int) { + output.writeU8(data.toUByte()) + } + + private fun shortCopy(offset: Int, size: Int) { + val s = size - 2 + writeControlBit(0) + writeControlBit(0) + writeControlBit(((s ushr 1) and 1) ) + writeControlBit((s and 1)) + writeU8(offset and 0xFF) + } + + private fun longCopy(offset: Int, size: Int) { + writeControlBit(0) + writeControlBit(1) + + if (size <= 9) { + writeU8(((offset shl 3) and 0xF8) or ((size - 2) and 0x07)) + writeU8((offset ushr 5) and 0xFF) + } else { + writeU8((offset shl 3) and 0xF8) + writeU8((offset ushr 5) and 0xFF) + writeU8(size - 1) + } + } +} diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsDecompress.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsDecompress.kt index 1539c7ba..6f551a56 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsDecompress.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/compression/prs/PrsDecompress.kt @@ -6,9 +6,9 @@ import world.phantasmal.core.PwResultBuilder import world.phantasmal.core.Severity import world.phantasmal.core.Success import world.phantasmal.lib.buffer.Buffer -import world.phantasmal.lib.cursor.BufferCursor import world.phantasmal.lib.cursor.Cursor import world.phantasmal.lib.cursor.WritableCursor +import world.phantasmal.lib.cursor.cursor import kotlin.math.floor import kotlin.math.min @@ -16,26 +16,27 @@ private val logger = KotlinLogging.logger {} fun prsDecompress(cursor: Cursor): PwResult { try { - val ctx = Context(cursor) + val decompressor = PrsDecompressor(cursor) + var i = 0 while (true) { - if (ctx.readFlagBit() == 1u) { + if (decompressor.readFlagBit() == 1) { // Single byte copy. - ctx.copyU8() + decompressor.copyU8() } else { // Multi byte copy. - var length: UInt + var length: Int var offset: Int - if (ctx.readFlagBit() == 0u) { + if (decompressor.readFlagBit() == 0) { // Short copy. - length = (ctx.readFlagBit() shl 1) or ctx.readFlagBit() - length += 2u + length = (decompressor.readFlagBit() shl 1) or decompressor.readFlagBit() + length += 2 - offset = ctx.readU8().toInt() - 256 + offset = decompressor.readU8().toInt() - 256 } else { // Long copy or end of file. - offset = ctx.readU16().toInt() + offset = decompressor.readU16().toInt() // Two zero bytes implies that this is the end of the file. if (offset == 0) { @@ -43,24 +44,26 @@ fun prsDecompress(cursor: Cursor): PwResult { } // Do we need to read a length byte, or is it encoded in what we already have? - length = (offset and 0b111).toUInt() - offset = offset shr 3 + length = offset and 0b111 + offset = offset ushr 3 - if (length == 0u) { - length = ctx.readU8().toUInt() - length += 1u + if (length == 0) { + length = decompressor.readU8().toInt() + length += 1 } else { - length += 2u + length += 2 } offset -= 8192 } - ctx.offsetCopy(offset, length) + decompressor.offsetCopy(offset, length) } + + i++ } - return Success(ctx.dst.seekStart(0u)) + return Success(decompressor.dst.seekStart(0)) } catch (e: Throwable) { return PwResultBuilder(logger) .addProblem(Severity.Error, "PRS-compressed stream is corrupt.", cause = e) @@ -68,23 +71,22 @@ fun prsDecompress(cursor: Cursor): PwResult { } } -class Context(cursor: Cursor) { +private class PrsDecompressor(cursor: Cursor) { private val src: Cursor = cursor - val dst: WritableCursor = BufferCursor( - Buffer.withCapacity(floor(1.5 * cursor.size.toDouble()).toUInt(), cursor.endianness), - ) - private var flags = 0u + val dst: WritableCursor = + Buffer.withCapacity(floor(1.5 * cursor.size.toDouble()).toInt(), cursor.endianness).cursor() + private var flags = 0 private var flagBitsLeft = 0 - fun readFlagBit(): UInt { + fun readFlagBit(): Int { // Fetch a new flag byte when the previous byte has been processed. if (flagBitsLeft == 0) { - flags = readU8().toUInt() + flags = readU8().toInt() flagBitsLeft = 8 } - val bit = flags and 1u - flags = flags shr 1 + val bit = flags and 1 + flags = flags ushr 1 flagBitsLeft -= 1 return bit } @@ -97,25 +99,26 @@ class Context(cursor: Cursor) { fun readU16(): UShort = src.u16() - fun offsetCopy(offset: Int, length: UInt) { + fun offsetCopy(offset: Int, length: Int) { require(offset in -8192..0) { "offset was ${offset}, should be between -8192 and 0." } - require(length in 1u..256u) { + require(length in 1..256) { "length was ${length}, should be between 1 and 256." } - // The length can be larger than -offset, in that case we copy -offset bytes size/-offset times. - val bufSize = min((-offset).toUInt(), length) + // The length can be larger than -offset, in that case we copy -offset bytes size/-offset + // times. + val bufSize = min(-offset, length) dst.seek(offset) val buf = dst.take(bufSize) - dst.seek(-offset - bufSize.toInt()) + dst.seek(-offset - bufSize) - repeat((length / bufSize).toInt()) { + repeat(length / bufSize) { dst.writeCursor(buf) - buf.seekStart(0u) + buf.seekStart(0) } dst.writeCursor(buf.take(length % bufSize)) diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/AbstractWritableCursor.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/AbstractWritableCursor.kt index 54436fee..a02836df 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/AbstractWritableCursor.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/AbstractWritableCursor.kt @@ -2,52 +2,51 @@ package world.phantasmal.lib.cursor import world.phantasmal.lib.ZERO_U16 import world.phantasmal.lib.ZERO_U8 -import world.phantasmal.lib.buffer.Buffer import kotlin.math.min abstract class AbstractWritableCursor -protected constructor(protected val offset: UInt) : WritableCursor { - override var position: UInt = 0u +protected constructor(protected val offset: Int) : WritableCursor { + override var position: Int = 0 protected set - override val bytesLeft: UInt + override val bytesLeft: Int get() = size - position - protected val absolutePosition: UInt + protected val absolutePosition: Int get() = offset + position - override fun hasBytesLeft(bytes: UInt): Boolean = + override fun hasBytesLeft(bytes: Int): Boolean = bytesLeft >= bytes override fun seek(offset: Int): WritableCursor = - seekStart((position.toInt() + offset).toUInt()) + seekStart(position + offset) - override fun seekStart(offset: UInt): WritableCursor { - require(offset <= size) { "Offset $offset is out of bounds." } + override fun seekStart(offset: Int): WritableCursor { + require(offset >= 0 || offset <= size) { "Offset $offset is out of bounds." } position = offset return this } - override fun seekEnd(offset: UInt): WritableCursor { - require(offset <= size) { "Offset $offset is out of bounds." } + override fun seekEnd(offset: Int): WritableCursor { + require(offset >= 0 || offset <= size) { "Offset $offset is out of bounds." } position = size - offset return this } override fun stringAscii( - maxByteLength: UInt, + maxByteLength: Int, nullTerminated: Boolean, dropRemaining: Boolean, ): String = buildString { - for (i in 0u until maxByteLength) { + for (i in 0 until maxByteLength) { val codePoint = u8() if (nullTerminated && codePoint == ZERO_U8) { if (dropRemaining) { - seek((maxByteLength - i - 1u).toInt()) + seek(maxByteLength - i - 1) } break @@ -58,19 +57,19 @@ protected constructor(protected val offset: UInt) : WritableCursor { } override fun stringUtf16( - maxByteLength: UInt, + maxByteLength: Int, nullTerminated: Boolean, dropRemaining: Boolean, ): String = buildString { - val len = maxByteLength / 2u + val len = maxByteLength / 2 - for (i in 0u until len) { + for (i in 0 until len) { val codePoint = u16() if (nullTerminated && codePoint == ZERO_U16) { if (dropRemaining) { - seek((maxByteLength - 2u * i - 2u).toInt()) + seek(maxByteLength - 2 * i - 2) } break @@ -82,7 +81,7 @@ protected constructor(protected val offset: UInt) : WritableCursor { override fun writeU8Array(array: UByteArray): WritableCursor { val len = array.size - requireSize(len.toUInt()) + requireSize(len) for (i in 0 until len) { writeU8(array[i]) @@ -93,7 +92,7 @@ protected constructor(protected val offset: UInt) : WritableCursor { override fun writeU16Array(array: UShortArray): WritableCursor { val len = array.size - requireSize(2u * len.toUInt()) + requireSize(2 * len) for (i in 0 until len) { writeU16(array[i]) @@ -104,7 +103,7 @@ protected constructor(protected val offset: UInt) : WritableCursor { override fun writeU32Array(array: UIntArray): WritableCursor { val len = array.size - requireSize(4u * len.toUInt()) + requireSize(4 * len) for (i in 0 until len) { writeU32(array[i]) @@ -115,7 +114,7 @@ protected constructor(protected val offset: UInt) : WritableCursor { override fun writeI32Array(array: IntArray): WritableCursor { val len = array.size - requireSize(4u * len.toUInt()) + requireSize(4 * len) for (i in 0 until len) { writeI32(array[i]) @@ -127,51 +126,45 @@ protected constructor(protected val offset: UInt) : WritableCursor { override fun writeCursor(other: Cursor): WritableCursor { val size = other.bytesLeft requireSize(size) - - for (i in 0u until (size / 4u)) { - writeU32(other.u32()) + for (i in 0 until size) { + writeI8(other.i8()) } - for (i in 0u until (size % 4u)) { - writeU8(other.u8()) - } - - position += size return this } - override fun writeStringAscii(str: String, byteLength: UInt): WritableCursor { + override fun writeStringAscii(str: String, byteLength: Int): WritableCursor { requireSize(byteLength) - val len = min(byteLength.toInt(), str.length) + val len = min(byteLength, str.length) for (i in 0 until len) { - writeU8(str[i].toByte().toUByte()) + writeI8(str[i].toByte()) } - val padLen = byteLength.toInt() - len + val padLen = byteLength - len for (i in 0 until padLen) { - writeU8(0u) + writeI8(0) } return this } - override fun writeStringUtf16(str: String, byteLength: UInt): WritableCursor { + override fun writeStringUtf16(str: String, byteLength: Int): WritableCursor { requireSize(byteLength) - val maxLen = byteLength.toInt() / 2 + val maxLen = byteLength / 2 val len = min(maxLen, str.length) for (i in 0 until len) { - writeU16(str[i].toShort().toUShort()) + writeI16(str[i].toShort()) } val padLen = maxLen - len for (i in 0 until padLen) { - writeU16(0u) + writeI16(0) } return this @@ -180,7 +173,7 @@ protected constructor(protected val offset: UInt) : WritableCursor { /** * Throws an error if less than [size] bytes are left at [position]. */ - protected fun requireSize(size: UInt) { + protected fun requireSize(size: Int) { val left = this.size - position require(size <= left) { "$size Bytes required but only $left available." } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/BufferCursor.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/BufferCursor.kt index f04a0be6..1b75fc9b 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/BufferCursor.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/BufferCursor.kt @@ -10,12 +10,12 @@ import world.phantasmal.lib.buffer.Buffer */ class BufferCursor( private val buffer: Buffer, - offset: UInt = 0u, - size: UInt = buffer.size - offset, + offset: Int = 0, + size: Int = buffer.size - offset, ) : AbstractWritableCursor(offset) { private var _size = size - override var size: UInt + override var size: Int get() = _size set(value) { if (value > _size) { @@ -52,13 +52,13 @@ class BufferCursor( override fun u16(): UShort { val r = buffer.getU16(absolutePosition) - position += 2u + position += 2 return r } override fun u32(): UInt { val r = buffer.getU32(absolutePosition) - position += 4u + position += 4 return r } @@ -70,28 +70,28 @@ class BufferCursor( override fun i16(): Short { val r = buffer.getI16(absolutePosition) - position += 2u + position += 2 return r } override fun i32(): Int { val r = buffer.getI32(absolutePosition) - position += 4u + position += 4 return r } override fun f32(): Float { val r = buffer.getF32(absolutePosition) - position += 4u + position += 4 return r } - override fun u8Array(n: UInt): UByteArray { + override fun u8Array(n: Int): UByteArray { requireSize(n) - val array = UByteArray(n.toInt()) + val array = UByteArray(n) - for (i in 0 until n.toInt()) { + for (i in 0 until n) { array[i] = buffer.getU8(absolutePosition) position++ } @@ -99,123 +99,123 @@ class BufferCursor( return array } - override fun u16Array(n: UInt): UShortArray { - requireSize(2u * n) + override fun u16Array(n: Int): UShortArray { + requireSize(2 * n) - val array = UShortArray(n.toInt()) + val array = UShortArray(n) - for (i in 0 until n.toInt()) { + for (i in 0 until n) { array[i] = buffer.getU16(absolutePosition) - position += 2u + position += 2 } return array } - override fun u32Array(n: UInt): UIntArray { - requireSize(4u * n) + override fun u32Array(n: Int): UIntArray { + requireSize(4 * n) - val array = UIntArray(n.toInt()) + val array = UIntArray(n) - for (i in 0 until n.toInt()) { + for (i in 0 until n) { array[i] = buffer.getU32(absolutePosition) - position += 4u + position += 4 } return array } - override fun i32Array(n: UInt): IntArray { - requireSize(4u * n) + override fun i32Array(n: Int): IntArray { + requireSize(4 * n) - val array = IntArray(n.toInt()) + val array = IntArray(n) - for (i in 0 until n.toInt()) { + for (i in 0 until n) { array[i] = buffer.getI32(absolutePosition) - position += 4u + position += 4 } return array } - override fun take(size: UInt): Cursor { + override fun take(size: Int): Cursor { val wrapper = BufferCursor(buffer, offset = absolutePosition, size) position += size return wrapper } - override fun buffer(size: UInt): Buffer { + override fun buffer(size: Int): Buffer { val wrapper = buffer.slice(offset = absolutePosition, size) position += size return wrapper } override fun writeU8(value: UByte): WritableCursor { - ensureSpace(1u) + ensureSpace(1) buffer.setU8(absolutePosition, value) position++ return this } override fun writeU16(value: UShort): WritableCursor { - ensureSpace(2u) + ensureSpace(2) buffer.setU16(absolutePosition, value) - position += 2u + position += 2 return this } override fun writeU32(value: UInt): WritableCursor { - ensureSpace(4u) + ensureSpace(4) buffer.setU32(absolutePosition, value) - position += 4u + position += 4 return this } override fun writeI8(value: Byte): WritableCursor { - ensureSpace(1u) + ensureSpace(1) buffer.setI8(absolutePosition, value) position++ return this } override fun writeI16(value: Short): WritableCursor { - ensureSpace(2u) + ensureSpace(2) buffer.setI16(absolutePosition, value) - position += 2u + position += 2 return this } override fun writeI32(value: Int): WritableCursor { - ensureSpace(4u) + ensureSpace(4) buffer.setI32(absolutePosition, value) - position += 4u + position += 4 return this } override fun writeF32(value: Float): WritableCursor { - ensureSpace(4u) + ensureSpace(4) buffer.setF32(absolutePosition, value) - position += 4u + position += 4 return this } override fun writeU8Array(array: UByteArray): WritableCursor { - ensureSpace(array.size.toUInt()) + ensureSpace(array.size) return super.writeU8Array(array) } override fun writeU16Array(array: UShortArray): WritableCursor { - ensureSpace(2u * array.size.toUInt()) + ensureSpace(2 * array.size) return super.writeU16Array(array) } override fun writeU32Array(array: UIntArray): WritableCursor { - ensureSpace(4u * array.size.toUInt()) + ensureSpace(4 * array.size) return super.writeU32Array(array) } override fun writeI32Array(array: IntArray): WritableCursor { - ensureSpace(4u * array.size.toUInt()) + ensureSpace(4 * array.size) return super.writeI32Array(array) } @@ -225,21 +225,21 @@ class BufferCursor( return super.writeCursor(other) } - override fun writeStringAscii(str: String, byteLength: UInt): WritableCursor { + override fun writeStringAscii(str: String, byteLength: Int): WritableCursor { ensureSpace(byteLength) return super.writeStringAscii(str, byteLength) } - override fun writeStringUtf16(str: String, byteLength: UInt): WritableCursor { + override fun writeStringUtf16(str: String, byteLength: Int): WritableCursor { ensureSpace(byteLength) return super.writeStringUtf16(str, byteLength) } - private fun ensureSpace(size: UInt) { - val needed = (position + size).toInt() - _size.toInt() + private fun ensureSpace(size: Int) { + val needed = (position + size) - _size if (needed > 0) { - _size += needed.toUInt() + _size += needed if (buffer.size < offset + _size) { buffer.size = offset + _size @@ -247,3 +247,6 @@ class BufferCursor( } } } + +fun Buffer.cursor(): BufferCursor = + BufferCursor(this) diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/Cursor.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/Cursor.kt index 2c8666f7..a73a5ca9 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/Cursor.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/Cursor.kt @@ -7,21 +7,21 @@ import world.phantasmal.lib.buffer.Buffer * A cursor for reading binary data. */ interface Cursor { - val size: UInt + val size: Int /** * The position from where bytes will be read or written. */ - val position: UInt + val position: Int /** * Byte order mode. */ var endianness: Endianness - val bytesLeft: UInt + val bytesLeft: Int - fun hasBytesLeft(bytes: UInt = 1u): Boolean + fun hasBytesLeft(bytes: Int = 1): Boolean /** * Seek forward or backward by a number of bytes. @@ -34,16 +34,16 @@ interface Cursor { /** * Seek forward from the start of the cursor by a number of bytes. * - * @param offset smaller than size + * @param offset greater or equal to 0 and smaller than size */ - fun seekStart(offset: UInt): Cursor + fun seekStart(offset: Int): Cursor /** * Seek backward from the end of the cursor by a number of bytes. * - * @param offset smaller than size + * @param offset greater or equal to 0 and smaller than size */ - fun seekEnd(offset: UInt): Cursor + fun seekEnd(offset: Int): Cursor /** * Reads an unsigned 8-bit integer and increments position by 1. @@ -83,36 +83,36 @@ interface Cursor { /** * Reads [n] unsigned 8-bit integers and increments position by [n]. */ - fun u8Array(n: UInt): UByteArray + fun u8Array(n: Int): UByteArray /** * Reads [n] unsigned 16-bit integers and increments position by 2[n]. */ - fun u16Array(n: UInt): UShortArray + fun u16Array(n: Int): UShortArray /** * Reads [n] unsigned 32-bit integers and increments position by 4[n]. */ - fun u32Array(n: UInt): UIntArray + fun u32Array(n: Int): UIntArray /** * Reads [n] signed 32-bit integers and increments position by 4[n]. */ - fun i32Array(n: UInt): IntArray + fun i32Array(n: Int): IntArray /** * Consumes a variable number of bytes. * * @param size the amount bytes to consume. - * @return a write-through view containing size bytes. + * @return a view containing size bytes. */ - fun take(size: UInt): Cursor + fun take(size: Int): Cursor /** * Consumes up to [maxByteLength] bytes. */ fun stringAscii( - maxByteLength: UInt, + maxByteLength: Int, nullTerminated: Boolean, dropRemaining: Boolean, ): String @@ -121,7 +121,7 @@ interface Cursor { * Consumes up to [maxByteLength] bytes. */ fun stringUtf16( - maxByteLength: UInt, + maxByteLength: Int, nullTerminated: Boolean, dropRemaining: Boolean, ): String @@ -129,5 +129,5 @@ interface Cursor { /** * Returns a buffer with a copy of [size] bytes at [position]. */ - fun buffer(size: UInt): Buffer + fun buffer(size: Int): Buffer } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/WritableCursor.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/WritableCursor.kt index 260e17b2..fa8fec6c 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/WritableCursor.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/cursor/WritableCursor.kt @@ -4,7 +4,13 @@ package world.phantasmal.lib.cursor * A cursor for reading and writing binary data. */ interface WritableCursor : Cursor { - override var size: UInt + override var size: Int + + override fun seek(offset: Int): WritableCursor + + override fun seekStart(offset: Int): WritableCursor + + override fun seekEnd(offset: Int): WritableCursor /** * Writes an unsigned 8-bit integer and increments position by 1. @@ -74,12 +80,12 @@ interface WritableCursor : Cursor { * Writes [byteLength] characters of [str]. If [str] is shorter than [byteLength], nul bytes * will be inserted until [byteLength] bytes have been written. */ - fun writeStringAscii(str: String, byteLength: UInt): WritableCursor + fun writeStringAscii(str: String, byteLength: Int): WritableCursor /** * Writes characters of [str] without writing more than [byteLength] bytes. If less than * [byteLength] bytes can be written this way, nul bytes will be inserted until [byteLength] * bytes have been written. */ - fun writeStringUtf16(str: String, byteLength: UInt): WritableCursor + fun writeStringUtf16(str: String, byteLength: Int): WritableCursor } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/Iff.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/Iff.kt index 155ae1ef..55a0a60e 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/Iff.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/Iff.kt @@ -7,9 +7,9 @@ import world.phantasmal.lib.cursor.Cursor private val logger = KotlinLogging.logger {} -class IffChunk(val type: UInt, val data: Cursor) +class IffChunk(val type: Int, val data: Cursor) -class IffChunkHeader(val type: UInt, val size: UInt) +class IffChunkHeader(val type: Int, val size: Int) /** * PSO uses a little endian variant of the IFF format. @@ -27,16 +27,16 @@ fun parseIffHeaders(cursor: Cursor): PwResult> = private fun parse( cursor: Cursor, - getChunk: (Cursor, type: UInt, size: UInt) -> T, + getChunk: (Cursor, type: Int, size: Int) -> T, ): PwResult> { val result = PwResult.build>(logger) val chunks = mutableListOf() var corrupted = false - while (cursor.bytesLeft >= 8u) { - val type = cursor.u32() + while (cursor.bytesLeft >= 8) { + val type = cursor.i32() val sizePos = cursor.position - val size = cursor.u32() + val size = cursor.i32() if (size > cursor.bytesLeft) { corrupted = true diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Ninja.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Ninja.kt index ba88d3b7..3efc154e 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Ninja.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Ninja.kt @@ -8,7 +8,7 @@ import world.phantasmal.lib.fileFormats.Vec3 import world.phantasmal.lib.fileFormats.parseIff import world.phantasmal.lib.fileFormats.vec3F32 -private const val NJCM: UInt = 0x4D434A4Eu +private const val NJCM: Int = 0x4D434A4E class NjObject( val evaluationFlags: NjEvaluationFlags, @@ -72,7 +72,7 @@ private fun parseSiblingObjects( val skip = (evalFlags and 0b1000000u) != 0u val shapeSkip = (evalFlags and 0b10000000u) != 0u - val modelOffset = cursor.u32() + val modelOffset = cursor.i32() val pos = cursor.vec3F32() val rotation = Vec3( angleToRad(cursor.i32()), @@ -80,24 +80,24 @@ private fun parseSiblingObjects( angleToRad(cursor.i32()), ) val scale = cursor.vec3F32() - val childOffset = cursor.u32() - val siblingOffset = cursor.u32() + val childOffset = cursor.i32() + val siblingOffset = cursor.i32() - val model = if (modelOffset == 0u) { + val model = if (modelOffset == 0) { null } else { cursor.seekStart(modelOffset) parse_model(cursor, context) } - val children = if (childOffset == 0u) { + val children = if (childOffset == 0) { emptyList() } else { cursor.seekStart(childOffset) parseSiblingObjects(cursor, parse_model, context) } - val siblings = if (siblingOffset == 0u) { + val siblings = if (siblingOffset == 0) { emptyList() } else { cursor.seekStart(siblingOffset) diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Nj.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Nj.kt index 00f8e44b..346081cf 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Nj.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/ninja/Nj.kt @@ -62,7 +62,7 @@ sealed class NjcmChunk(val typeId: UByte) { class Bits(typeId: UByte, val srcAlpha: UByte, val dstAlpha: UByte) : NjcmChunk(typeId) - class CachePolygonList(val cacheIndex: UByte, val offset: UInt) : NjcmChunk(4u) + class CachePolygonList(val cacheIndex: UByte, val offset: Int) : NjcmChunk(4u) class DrawPolygonList(val cacheIndex: UByte) : NjcmChunk(5u) @@ -122,15 +122,15 @@ class NjcmErgb( val b: UByte, ) -fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap): NjcmModel { - val vlistOffset = cursor.u32() // Vertex list - val plistOffset = cursor.u32() // Triangle strip index list +fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap): NjcmModel { + val vlistOffset = cursor.i32() // Vertex list + val plistOffset = cursor.i32() // Triangle strip index list val boundingSphereCenter = cursor.vec3F32() val boundingSphereRadius = cursor.f32() val vertices: MutableList = mutableListOf() val meshes: MutableList = mutableListOf() - if (vlistOffset != 0u) { + if (vlistOffset != 0) { cursor.seekStart(vlistOffset) for (chunk in parseChunks(cursor, cachedChunkOffsets, true)) { @@ -148,7 +148,7 @@ fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap): } } - if (plistOffset != 0u) { + if (plistOffset != 0) { cursor.seekStart(plistOffset) var textureId: UInt? = null @@ -203,7 +203,7 @@ fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap): // TODO: don't reparse when DrawPolygonList chunk is encountered. private fun parseChunks( cursor: Cursor, - cachedChunkOffsets: MutableMap, + cachedChunkOffsets: MutableMap, wideEndChunks: Boolean, ): List { val chunks: MutableList = mutableListOf() @@ -214,7 +214,7 @@ private fun parseChunks( val flags = cursor.u8() val flagsUInt = flags.toUInt() val chunkStartPosition = cursor.position - var size = 0u + var size = 0 when (typeId.toInt()) { 0 -> { @@ -253,7 +253,7 @@ private fun parseChunks( )) } in 8..9 -> { - size = 2u + size = 2 val textureBitsAndId = cursor.u16().toUInt() chunks.add(NjcmChunk.Tiny( @@ -269,7 +269,7 @@ private fun parseChunks( )) } in 17..31 -> { - size = 2u + 2u * cursor.u16() + size = 2 + 2 * cursor.i16() var diffuse: NjcmArgb? = null var ambient: NjcmArgb? = null @@ -312,32 +312,32 @@ private fun parseChunks( )) } in 32..50 -> { - size = 2u + 4u * cursor.u16() + size = 2 + 4 * cursor.i16() chunks.add(NjcmChunk.Vertex( typeId, vertices = parseVertexChunk(cursor, typeId, flags), )) } in 56..58 -> { - size = 2u + 2u * cursor.u16() + size = 2 + 2 * cursor.i16() chunks.add(NjcmChunk.Volume( typeId, )) } in 64..75 -> { - size = 2u + 2u * cursor.u16() + size = 2 + 2 * cursor.i16() chunks.add(NjcmChunk.Strip( typeId, triangleStrips = parseTriangleStripChunk(cursor, typeId, flags), )) } 255 -> { - size = if (wideEndChunks) 2u else 0u + size = if (wideEndChunks) 2 else 0 chunks.add(NjcmChunk.End) loop = false } else -> { - size = 2u + 2u * cursor.u16() + size = 2 + 2 * cursor.i16() chunks.add(NjcmChunk.Unknown( typeId, )) diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Bin.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Bin.kt index 68a4ec4c..9da37b96 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Bin.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Bin.kt @@ -6,9 +6,9 @@ import world.phantasmal.lib.cursor.Cursor private val logger = KotlinLogging.logger {} -private const val DC_GC_OBJECT_CODE_OFFSET = 468u -private const val PC_OBJECT_CODE_OFFSET = 920u -private const val BB_OBJECT_CODE_OFFSET = 4652u +private const val DC_GC_OBJECT_CODE_OFFSET = 468 +private const val PC_OBJECT_CODE_OFFSET = 920 +private const val BB_OBJECT_CODE_OFFSET = 4652 class BinFile( val format: BinFormat, @@ -40,17 +40,19 @@ enum class BinFormat { } fun parseBin(cursor: Cursor): BinFile { - val objectCodeOffset = cursor.u32() - val labelOffsetTableOffset = cursor.u32() // Relative offsets - val size = cursor.u32() + val objectCodeOffset = cursor.i32() + val labelOffsetTableOffset = cursor.i32() // Relative offsets + val size = cursor.i32() cursor.seek(4) // Always seems to be 0xFFFFFFFF. val format = when (objectCodeOffset) { DC_GC_OBJECT_CODE_OFFSET -> BinFormat.DC_GC - BB_OBJECT_CODE_OFFSET -> BinFormat.BB PC_OBJECT_CODE_OFFSET -> BinFormat.PC + BB_OBJECT_CODE_OFFSET -> BinFormat.BB else -> { - logger.warn { "Object code at unexpected offset, assuming file is a PC file." } + logger.warn { + "Object code at unexpected offset $objectCodeOffset, assuming file is a PC file." + } BinFormat.PC } } @@ -65,15 +67,15 @@ fun parseBin(cursor: Cursor): BinFile { cursor.seek(1) language = cursor.u8().toUInt() questId = cursor.u16().toUInt() - questName = cursor.stringAscii(32u, nullTerminated = true, dropRemaining = true) - shortDescription = cursor.stringAscii(128u, nullTerminated = true, dropRemaining = true) - longDescription = cursor.stringAscii(288u, nullTerminated = true, dropRemaining = true) + questName = cursor.stringAscii(32, nullTerminated = true, dropRemaining = true) + shortDescription = cursor.stringAscii(128, nullTerminated = true, dropRemaining = true) + longDescription = cursor.stringAscii(288, nullTerminated = true, dropRemaining = true) } else { questId = cursor.u32() language = cursor.u32() - questName = cursor.stringUtf16(64u, nullTerminated = true, dropRemaining = true) - shortDescription = cursor.stringUtf16(256u, nullTerminated = true, dropRemaining = true) - longDescription = cursor.stringUtf16(576u, nullTerminated = true, dropRemaining = true) + questName = cursor.stringUtf16(64, nullTerminated = true, dropRemaining = true) + shortDescription = cursor.stringUtf16(256, nullTerminated = true, dropRemaining = true) + longDescription = cursor.stringUtf16(576, nullTerminated = true, dropRemaining = true) } if (size != cursor.size) { @@ -82,12 +84,12 @@ fun parseBin(cursor: Cursor): BinFile { val shopItems = if (format == BinFormat.BB) { cursor.seek(4) // Skip padding. - cursor.u32Array(932u) + cursor.u32Array(932) } else { UIntArray(0) } - val labelOffsetCount = (cursor.size - labelOffsetTableOffset) / 4u + val labelOffsetCount = (cursor.size - labelOffsetTableOffset) / 4 val labelOffsets = cursor .seekStart(labelOffsetTableOffset) .i32Array(labelOffsetCount) diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Dat.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Dat.kt index a804c17f..67a63db7 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Dat.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/Dat.kt @@ -6,13 +6,13 @@ import world.phantasmal.lib.cursor.Cursor private val logger = KotlinLogging.logger {} -private const val EVENT_ACTION_SPAWN_NPCS: UByte = 0x8u -private const val EVENT_ACTION_UNLOCK: UByte = 0xAu -private const val EVENT_ACTION_LOCK: UByte = 0xBu -private const val EVENT_ACTION_TRIGGER_EVENT: UByte = 0xCu +private const val EVENT_ACTION_SPAWN_NPCS = 0x8 +private const val EVENT_ACTION_UNLOCK = 0xA +private const val EVENT_ACTION_LOCK = 0xB +private const val EVENT_ACTION_TRIGGER_EVENT = 0xC -const val OBJECT_BYTE_SIZE = 68u -const val NPC_BYTE_SIZE = 72u +const val OBJECT_BYTE_SIZE = 68 +const val NPC_BYTE_SIZE = 72 class DatFile( val objs: List, @@ -22,7 +22,7 @@ class DatFile( ) class DatEntity( - var areaId: UInt, + var areaId: Int, val data: Buffer, ) @@ -32,7 +32,7 @@ class DatEvent( var wave: UShort, var delay: UShort, val actions: MutableList, - val areaId: UInt, + val areaId: Int, val unknown: UShort, ) @@ -56,10 +56,10 @@ sealed class DatEventAction { } class DatUnknown( - val entityType: UInt, - val totalSize: UInt, - val areaId: UInt, - val entitiesSize: UInt, + val entityType: Int, + val totalSize: Int, + val areaId: Int, + val entitiesSize: Int, val data: UByteArray, ) @@ -70,24 +70,24 @@ fun parseDat(cursor: Cursor): DatFile { val unknowns = mutableListOf() while (cursor.hasBytesLeft()) { - val entityType = cursor.u32() - val totalSize = cursor.u32() - val areaId = cursor.u32() - val entitiesSize = cursor.u32() + val entityType = cursor.i32() + val totalSize = cursor.i32() + val areaId = cursor.i32() + val entitiesSize = cursor.i32() - if (entityType == 0u) { + if (entityType == 0) { break } else { - require(entitiesSize == totalSize - 16u) { - "Malformed DAT file. Expected an entities size of ${totalSize - 16u}, got ${entitiesSize}." + require(entitiesSize == totalSize - 16) { + "Malformed DAT file. Expected an entities size of ${totalSize - 16}, got ${entitiesSize}." } val entitiesCursor = cursor.take(entitiesSize) when (entityType) { - 1u -> parseEntities(entitiesCursor, areaId, objs, OBJECT_BYTE_SIZE) - 2u -> parseEntities(entitiesCursor, areaId, npcs, NPC_BYTE_SIZE) - 3u -> parseEvents(entitiesCursor, areaId, events) + 1 -> parseEntities(entitiesCursor, areaId, objs, OBJECT_BYTE_SIZE) + 2 -> parseEntities(entitiesCursor, areaId, npcs, NPC_BYTE_SIZE) + 3 -> parseEvents(entitiesCursor, areaId, events) else -> { // Unknown entity types 4 and 5 (challenge mode). unknowns.add(DatUnknown( @@ -118,13 +118,13 @@ fun parseDat(cursor: Cursor): DatFile { private fun parseEntities( cursor: Cursor, - areaId: UInt, + areaId: Int, entities: MutableList, - entitySize: UInt, + entitySize: Int, ) { val entityCount = cursor.size / entitySize - repeat(entityCount.toInt()) { + repeat(entityCount) { entities.add(DatEntity( areaId, data = cursor.buffer(entitySize), @@ -132,29 +132,29 @@ private fun parseEntities( } } -private fun parseEvents(cursor: Cursor, areaId: UInt, events: MutableList) { - val actionsOffset = cursor.u32() +private fun parseEvents(cursor: Cursor, areaId: Int, events: MutableList) { + val actionsOffset = cursor.i32() cursor.seek(4) // Always 0x10 - val eventCount = cursor.u32() + val eventCount = cursor.i32() cursor.seek(3) // Always 0 val eventType = cursor.u8() - require(eventType == (0x32u).toUByte()) { + require(eventType != (0x32u).toUByte()) { "Can't parse challenge mode quests yet." } cursor.seekStart(actionsOffset) val actionsCursor = cursor.take(cursor.bytesLeft) - cursor.seekStart(16u) + cursor.seekStart(16) - repeat(eventCount.toInt()) { + repeat(eventCount) { val id = cursor.u32() cursor.seek(4) // Always 0x100 val sectionId = cursor.u16() val wave = cursor.u16() val delay = cursor.u16() val unknown = cursor.u16() // "wavesetting"? - val eventActionsOffset = cursor.u32() + val eventActionsOffset = cursor.i32() val actions: MutableList = if (eventActionsOffset < actionsCursor.size) { @@ -178,7 +178,7 @@ private fun parseEvents(cursor: Cursor, areaId: UInt, events: MutableList { val actions = mutableListOf() outer@ while (cursor.hasBytesLeft()) { - when (val type = cursor.u8()) { - (1u).toUByte() -> break@outer + when (val type = cursor.u8().toInt()) { + 1 -> break@outer EVENT_ACTION_SPAWN_NPCS -> actions.add(DatEventAction.SpawnNpcs( diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt index 86c186b4..dd133965 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt @@ -67,7 +67,7 @@ fun parseObjectCode( // Put segments in an array and parse left-over segments as data. var offset = 0 - while (offset < cursor.size.toInt()) { + while (offset < cursor.size) { var segment: Segment? = offsetToSegment[offset] // If we have a segment, add it. Otherwise create a new data segment. @@ -76,7 +76,7 @@ fun parseObjectCode( var endOffset: Int if (labels == null) { - endOffset = cursor.size.toInt() + endOffset = cursor.size for (label in labelHolder.labels) { if (label.offset > offset) { @@ -86,10 +86,10 @@ fun parseObjectCode( } } else { val info = labelHolder.getInfo(labels[0])!! - endOffset = info.next?.offset ?: cursor.size.toInt() + endOffset = info.next?.offset ?: cursor.size } - cursor.seekStart(offset.toUInt()) + cursor.seekStart(offset) parseDataSegment( offsetToSegment, cursor, @@ -110,7 +110,7 @@ fun parseObjectCode( offset += when (segment) { is InstructionSegment -> segment.instructions.sumBy { instructionSize(it, dcGcFormat) } - is DataSegment -> segment.data.size.toInt() + is DataSegment -> segment.data.size // String segments should be multiples of 4 bytes. is StringSegment -> 4 * ceil((segment.value.length + 1) / 2.0).toInt() @@ -136,7 +136,7 @@ fun parseObjectCode( } // Sanity check parsed object code. - if (cursor.size != offset.toUInt()) { + if (cursor.size != offset) { result.addProblem( Severity.Error, "The script code is corrupt.", @@ -201,7 +201,8 @@ private fun findAndParseSegments( // Never on the stack. // Eat all remaining arguments. while (i < instruction.args.size) { - newLabels[instruction.args[i].value as Int] = SegmentType.Instructions + newLabels[instruction.args[i].value as Int] = + SegmentType.Instructions i++ } } @@ -322,8 +323,8 @@ private fun parseSegment( } } - val endOffset = info.next?.offset ?: cursor.size.toInt() - cursor.seekStart(info.offset.toUInt()) + val endOffset = info.next?.offset ?: cursor.size + cursor.seekStart(info.offset) return when (type) { SegmentType.Instructions -> @@ -370,9 +371,9 @@ private fun parseInstructionsSegment( instructions, SegmentSrcLoc() ) - offsetToSegment[cursor.position.toInt()] = segment + offsetToSegment[cursor.position] = segment - while (cursor.position < endOffset.toUInt()) { + while (cursor.position < endOffset) { // Parse the opcode. val mainOpcode = cursor.u8() @@ -436,10 +437,10 @@ private fun parseDataSegment( val startOffset = cursor.position val segment = DataSegment( labels, - cursor.buffer(endOffset.toUInt() - startOffset), + cursor.buffer(endOffset - startOffset), SegmentSrcLoc(), ) - offsetToSegment[startOffset.toInt()] = segment + offsetToSegment[startOffset] = segment } private fun parseStringSegment( @@ -454,20 +455,20 @@ private fun parseStringSegment( labels, if (dcGcFormat) { cursor.stringAscii( - endOffset.toUInt() - startOffset, + endOffset - startOffset, nullTerminated = true, dropRemaining = true ) } else { cursor.stringUtf16( - endOffset.toUInt() - startOffset, + endOffset - startOffset, nullTerminated = true, dropRemaining = true ) }, SegmentSrcLoc() ) - offsetToSegment[startOffset.toInt()] = segment + offsetToSegment[startOffset] = segment } private fun parseInstructionArguments( @@ -501,7 +502,7 @@ private fun parseInstructionArguments( } is StringType -> { - val maxBytes = min(4096u, cursor.bytesLeft) + val maxBytes = min(4096, cursor.bytesLeft) args.add(Arg( if (dcGcFormat) { cursor.stringAscii( @@ -521,7 +522,7 @@ private fun parseInstructionArguments( is ILabelVarType -> { val argSize = cursor.u8() - args.addAll(cursor.u16Array(argSize.toUInt()).map { Arg(it.toInt()) }) + args.addAll(cursor.u16Array(argSize.toInt()).map { Arg(it.toInt()) }) } is RegRefType, @@ -532,7 +533,7 @@ private fun parseInstructionArguments( is RegRefVarType -> { val argSize = cursor.u8() - args.addAll(cursor.u8Array(argSize.toUInt()).map { Arg(it.toInt()) }) + args.addAll(cursor.u8Array(argSize.toInt()).map { Arg(it.toInt()) }) } else -> error("Parameter type ${param.type} not implemented.") diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestNpc.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestNpc.kt index 93f44792..ad1e512f 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestNpc.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestNpc.kt @@ -8,15 +8,15 @@ class QuestNpc(var episode: Episode, var areaId: Int, val data: Buffer) { * Only seems to be valid for non-enemies. */ var scriptLabel: Int - get() = data.getF32(60u).roundToInt() + get() = data.getF32(60).roundToInt() set(value) { - data.setF32(60u, value.toFloat()) + data.setF32(60, value.toFloat()) } var skin: Int - get() = data.getI32(64u) + get() = data.getI32(64) set(value) { - data.setI32(64u, value) + data.setI32(64, value) } init { diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestObject.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestObject.kt index 83181503..36932d14 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestObject.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/QuestObject.kt @@ -3,9 +3,11 @@ package world.phantasmal.lib.fileFormats.quest import world.phantasmal.lib.buffer.Buffer class QuestObject(var areaId: Int, val data: Buffer) { - var type: ObjectType = TODO() - val scriptLabel: Int? = TODO() - val scriptLabel2: Int? = TODO() + var type: ObjectType + get() = TODO() + set(_) = TODO() + val scriptLabel: Int? = null // TODO Implement scriptLabel. + val scriptLabel2: Int? = null // TODO Implement scriptLabel2. init { require(data.size == OBJECT_BYTE_SIZE) { diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/buffer/BufferTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/buffer/BufferTests.kt index e1f3ea62..8d0fa4ff 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/buffer/BufferTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/buffer/BufferTests.kt @@ -7,29 +7,56 @@ import kotlin.test.assertTrue class BufferTests { @Test - fun simple_properties_and_invariants() { - val capacity = 500u + fun withCapacity() { + val capacity = 500 val buffer = Buffer.withCapacity(capacity) - assertEquals(0u, buffer.size) + assertEquals(0, buffer.size) assertEquals(capacity, buffer.capacity) assertEquals(Endianness.Little, buffer.endianness) } + @Test + fun withSize() { + val size = 500 + val buffer = Buffer.withSize(size) + + assertEquals(size, buffer.size) + assertEquals(size, buffer.capacity) + assertEquals(Endianness.Little, buffer.endianness) + } + @Test fun reallocates_internal_storage_when_necessary() { - val buffer = Buffer.withCapacity(100u) + val buffer = Buffer.withCapacity(100) - assertEquals(0u, buffer.size) - assertEquals(100u, buffer.capacity) + assertEquals(0, buffer.size) + assertEquals(100, buffer.capacity) - buffer.size = 101u + buffer.size = 101 - assertEquals(101u, buffer.size) - assertTrue(buffer.capacity >= 101u) + assertEquals(101, buffer.size) + assertTrue(buffer.capacity >= 101) - buffer.setU8(100u, (0xABu).toUByte()) + buffer.setU8(100, (0xABu).toUByte()) - assertEquals(0xABu, buffer.getU8(100u).toUInt()) + assertEquals(0xABu, buffer.getU8(100).toUInt()) + } + + @Test + fun fill_and_zero() { + val buffer = Buffer.withSize(100) + + buffer.fill(100) + + for (i in 0 until buffer.size) { + assertEquals(100u, buffer.getU8(i)) + } + + buffer.zero() + + for (i in 0 until buffer.size) { + assertEquals(0u, buffer.getU8(i)) + } } } diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/compression/prs/PrsCompressTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/compression/prs/PrsCompressTests.kt new file mode 100644 index 00000000..c78cdfd3 --- /dev/null +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/compression/prs/PrsCompressTests.kt @@ -0,0 +1,74 @@ +package world.phantasmal.lib.compression.prs + +import world.phantasmal.lib.buffer.Buffer +import world.phantasmal.lib.cursor.cursor +import kotlin.random.Random +import kotlin.random.nextUInt +import kotlin.test.Test +import kotlin.test.assertEquals + +class PrsCompressTests { + @Test + fun edge_case_0_bytes() { + val compressed = prsCompress(Buffer.withSize(0).cursor()) + + assertEquals(3, compressed.size) + } + + @Test + fun edge_case_1_byte() { + val compressed = prsCompress(Buffer.withSize(1).fill(111).cursor()) + + assertEquals(4, compressed.size) + } + + @Test + fun edge_case_2_bytes() { + val compressed = prsCompress(Buffer.fromByteArray(byteArrayOf(7, 111)).cursor()) + + assertEquals(5, compressed.size) + } + + @Test + fun edge_case_3_bytes() { + val compressed = prsCompress(Buffer.fromByteArray(byteArrayOf(7, 55, 120)).cursor()) + + assertEquals(6, compressed.size) + } + + @Test + fun best_case() { + val compressed = prsCompress(Buffer.withSize(10_000).fill(127).cursor()) + + assertEquals(475, compressed.size) + } + + @Test + fun worst_case() { + val random = Random(37) + val buffer = Buffer.withSize(10_000) + + for (i in 0 until buffer.size step 4) { + buffer.setU32(i, random.nextUInt()) + } + + val compressed = prsCompress(buffer.cursor()) + + assertEquals(11252, compressed.size) + } + + @Test + fun typical_case() { + val random = Random(37) + val pattern = byteArrayOf(0, 0, 2, 0, 3, 0, 5, 0, 0, 0, 7, 9, 11, 13, 0, 0) + val buffer = Buffer.withSize(1000 * pattern.size) + + for (i in 0 until buffer.size) { + buffer.setI8(i, (pattern[i % pattern.size] + random.nextInt(10)).toByte()) + } + + val compressed = prsCompress(buffer.cursor()) + + assertEquals(14549, compressed.size) + } +} diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/compression/prs/PrsDecompressTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/compression/prs/PrsDecompressTests.kt new file mode 100644 index 00000000..40b27f51 --- /dev/null +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/compression/prs/PrsDecompressTests.kt @@ -0,0 +1,84 @@ +package world.phantasmal.lib.compression.prs + +import world.phantasmal.lib.buffer.Buffer +import world.phantasmal.lib.cursor.cursor +import kotlin.random.Random +import kotlin.random.nextUInt +import kotlin.test.Test +import kotlin.test.assertEquals + +class PrsDecompressTests { + @Test + fun edge_case_0_bytes() { + testWithBuffer(Buffer.withSize(0)) + } + + @Test + fun edge_case_1_byte() { + testWithBuffer(Buffer.withSize(1).fill(111)) + } + + @Test + fun edge_case_2_bytes() { + testWithBuffer(Buffer.fromByteArray(byteArrayOf(7, 111))) + } + + @Test + fun edge_case_3_bytes() { + testWithBuffer(Buffer.fromByteArray(byteArrayOf(7, 55, 120))) + } + + @Test + fun best_case() { + testWithBuffer(Buffer.withSize(10_000).fill(127)) + } + + @Test + fun worst_case() { + val random = Random(37) + val buffer = Buffer.withSize(10_000) + + for (i in 0 until buffer.size step 4) { + buffer.setU32(i, random.nextUInt()) + } + + testWithBuffer(buffer) + } + + @Test + fun typical_case() { + val random = Random(37) + val pattern = byteArrayOf(0, 0, 2, 0, 3, 0, 5, 0, 0, 0, 7, 9, 11, 13, 0, 0) + val buffer = Buffer.withSize(1000 * pattern.size) + + for (i in 0 until buffer.size) { + buffer.setI8(i, (pattern[i % pattern.size] + random.nextInt(10)).toByte()) + } + + testWithBuffer(buffer) + } + + private fun testWithBuffer(buffer: Buffer) { + val cursor = buffer.cursor() + val compressedCursor = prsCompress(cursor) + + val decompressedCursor = prsDecompress(compressedCursor).unwrap() + cursor.seekStart(0) + + assertEquals(cursor.size, decompressedCursor.size) + + while (cursor.hasBytesLeft()) { + val expected = cursor.i8() + val actual = decompressedCursor.i8() + + if (expected != actual) { + // Assert after check for performance. + assertEquals( + expected, + actual, + "Got $actual, expected $expected at ${cursor.position - 1}." + ) + } + } + } +} diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/BufferCursorTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/BufferCursorTests.kt index ef6f0aa3..8dc40df6 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/BufferCursorTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/BufferCursorTests.kt @@ -53,20 +53,20 @@ class BufferCursorTests : WritableCursorTests() { val expectedNumber1 = 7891378 val expectedNumber2 = 893894273 - val buffer = Buffer.withCapacity(8u, endianness) + val buffer = Buffer.withCapacity(8, endianness) val cursor = BufferCursor(buffer) - assertEquals(0u, buffer.size) - assertEquals(0u, cursor.size) + assertEquals(0, buffer.size) + assertEquals(0, cursor.size) cursor.write(expectedNumber1) - assertEquals(byteCount.toUInt(), buffer.size) - assertEquals(byteCount.toUInt(), cursor.size) + assertEquals(byteCount, buffer.size) + assertEquals(byteCount, cursor.size) cursor.write(expectedNumber2) - assertEquals(2u * byteCount.toUInt(), buffer.size) - assertEquals(2u * byteCount.toUInt(), cursor.size) + assertEquals(2 * byteCount, buffer.size) + assertEquals(2 * byteCount, cursor.size) } } diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/CursorTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/CursorTests.kt index e188ac62..8a421645 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/CursorTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/CursorTests.kt @@ -21,15 +21,15 @@ abstract class CursorTests { val cursor = createCursor(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), endianness) for ((seek_to, expectedPos) in listOf( - 0 to 0u, - 3 to 3u, - 5 to 8u, - 2 to 10u, - -10 to 0u, + 0 to 0, + 3 to 3, + 5 to 8, + 2 to 10, + -10 to 0, )) { cursor.seek(seek_to) - assertEquals(10u, cursor.size) + assertEquals(10, cursor.size) assertEquals(expectedPos, cursor.position) assertEquals(cursor.position + cursor.bytesLeft, cursor.size) assertEquals(endianness, cursor.endianness) @@ -116,10 +116,10 @@ abstract class CursorTests { val cursor = createCursor(bytes, endianness) assertEquals(expectedNumber1, cursor.read()) - assertEquals(byteCount.toUInt(), cursor.position) + assertEquals(byteCount, cursor.position) assertEquals(expectedNumber2, cursor.read()) - assertEquals(2u * byteCount.toUInt(), cursor.position) + assertEquals(2 * byteCount, cursor.position) } @Test @@ -139,17 +139,17 @@ abstract class CursorTests { val cursor = createCursor(bytes, endianness) assertEquals(2.5f, cursor.f32()) - assertEquals(4u, cursor.position) + assertEquals(4, cursor.position) assertEquals(32.25f, cursor.f32()) - assertEquals(8u, cursor.position) + assertEquals(8, cursor.position) } @Test fun u8Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = u8Array(n) - IntArray(n.toInt()) { arr[it].toInt() } + IntArray(n) { arr[it].toInt() } } testIntegerArrayRead(1, read, Endianness.Little) @@ -158,9 +158,9 @@ abstract class CursorTests { @Test fun u16Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = u16Array(n) - IntArray(n.toInt()) { arr[it].toInt() } + IntArray(n) { arr[it].toInt() } } testIntegerArrayRead(2, read, Endianness.Little) @@ -169,9 +169,9 @@ abstract class CursorTests { @Test fun u32Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = u32Array(n) - IntArray(n.toInt()) { arr[it].toInt() } + IntArray(n) { arr[it].toInt() } } testIntegerArrayRead(4, read, Endianness.Little) @@ -180,9 +180,9 @@ abstract class CursorTests { @Test fun i32Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = i32Array(n) - IntArray(n.toInt()) { arr[it] } + IntArray(n) { arr[it] } } testIntegerArrayRead(4, read, Endianness.Little) @@ -191,7 +191,7 @@ abstract class CursorTests { private fun testIntegerArrayRead( byteCount: Int, - read: Cursor.(UInt) -> IntArray, + read: Cursor.(Int) -> IntArray, endianness: Endianness, ) { // Generate array of the form 1, 2, 0xFF, 4, 5, 6, 7, 8. @@ -217,26 +217,47 @@ abstract class CursorTests { // Test cursor. val cursor = createCursor(bytes, endianness) - val array1 = cursor.read(3u) + val array1 = cursor.read(3) assertEquals(1, array1[0]) assertEquals(2, array1[1]) assertEquals(allOnes, array1[2]) - assertEquals(3u * byteCount.toUInt(), cursor.position) + assertEquals(3 * byteCount, cursor.position) - cursor.seekStart((2 * byteCount).toUInt()) - val array2 = cursor.read(4u) + cursor.seekStart(2 * byteCount) + val array2 = cursor.read(4) assertEquals(allOnes, array2[0]) assertEquals(4, array2[1]) assertEquals(5, array2[2]) assertEquals(6, array2[3]) - assertEquals(6u * byteCount.toUInt(), cursor.position) + assertEquals(6 * byteCount, cursor.position) - cursor.seekStart((5 * byteCount).toUInt()) - val array3 = cursor.read(3u) + cursor.seekStart(5 * byteCount) + val array3 = cursor.read(3) assertEquals(6, array3[0]) assertEquals(7, array3[1]) assertEquals(8, array3[2]) - assertEquals(8u * byteCount.toUInt(), cursor.position) + assertEquals(8 * byteCount, cursor.position) + } + + @Test + fun take() { + testTake(Endianness.Little) + testTake(Endianness.Big) + } + + private fun testTake(endianness: Endianness) { + val bytes = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8) + + val cursor = createCursor(bytes, endianness) + + val newCursor = cursor.seek(2).take(4) + + assertEquals(6, cursor.position) + assertEquals(4, newCursor.size) + assertEquals(3u, newCursor.u8()) + assertEquals(4u, newCursor.u8()) + assertEquals(5u, newCursor.u8()) + assertEquals(6u, newCursor.u8()) } @Test @@ -254,7 +275,7 @@ abstract class CursorTests { private fun testStringRead( byteCount: Int, read: Cursor.( - maxByteLength: UInt, + maxByteLength: Int, nullTerminated: Boolean, dropRemaining: Boolean, ) -> String, @@ -271,30 +292,29 @@ abstract class CursorTests { } } - val bc = byteCount.toUInt() val cursor = createCursor(bytes, endianness) - cursor.seekStart(bc) - assertEquals("AB", cursor.read(4u * bc, true, true)) - assertEquals(5u * bc, cursor.position) - cursor.seekStart(bc) - assertEquals("AB", cursor.read(2u * bc, true, true)) - assertEquals(3u * bc, cursor.position) + cursor.seekStart(byteCount) + assertEquals("AB", cursor.read(4 * byteCount, true, true)) + assertEquals(5 * byteCount, cursor.position) + cursor.seekStart(byteCount) + assertEquals("AB", cursor.read(2 * byteCount, true, true)) + assertEquals(3 * byteCount, cursor.position) - cursor.seekStart(bc) - assertEquals("AB", cursor.read(4u * bc, true, false)) - assertEquals(4u * bc, cursor.position) - cursor.seekStart(bc) - assertEquals("AB", cursor.read(2u * bc, true, false)) - assertEquals(3u * bc, cursor.position) + cursor.seekStart(byteCount) + assertEquals("AB", cursor.read(4 * byteCount, true, false)) + assertEquals(4 * byteCount, cursor.position) + cursor.seekStart(byteCount) + assertEquals("AB", cursor.read(2 * byteCount, true, false)) + assertEquals(3 * byteCount, cursor.position) - cursor.seekStart(bc) - assertEquals("AB\u0000ÿ", cursor.read(4u * bc, false, true)) - assertEquals(5u * bc, cursor.position) + cursor.seekStart(byteCount) + assertEquals("AB\u0000ÿ", cursor.read(4 * byteCount, false, true)) + assertEquals(5 * byteCount, cursor.position) - cursor.seekStart(bc) - assertEquals("AB\u0000ÿ", cursor.read(4u * bc, false, false)) - assertEquals(5u * bc, cursor.position) + cursor.seekStart(byteCount) + assertEquals("AB\u0000ÿ", cursor.read(4 * byteCount, false, false)) + assertEquals(5 * byteCount, cursor.position) } @Test @@ -308,13 +328,13 @@ abstract class CursorTests { val cursor = createCursor(bytes, endianness) - val buf = cursor.seek(2).buffer(4u) + val buf = cursor.seek(2).buffer(4) - assertEquals(6u, cursor.position) - assertEquals(4u, buf.size) - assertEquals(3u, buf.getU8(0u)) - assertEquals(4u, buf.getU8(1u)) - assertEquals(5u, buf.getU8(2u)) - assertEquals(6u, buf.getU8(3u)) + assertEquals(6, cursor.position) + assertEquals(4, buf.size) + assertEquals(3u, buf.getU8(0)) + assertEquals(4u, buf.getU8(1)) + assertEquals(5u, buf.getU8(2)) + assertEquals(6u, buf.getU8(3)) } } diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/WritableCursorTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/WritableCursorTests.kt index e03b9308..497ba234 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/WritableCursorTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/cursor/WritableCursorTests.kt @@ -1,6 +1,7 @@ package world.phantasmal.lib.cursor import world.phantasmal.lib.Endianness +import world.phantasmal.lib.buffer.Buffer import kotlin.math.abs import kotlin.test.Test import kotlin.test.assertEquals @@ -18,15 +19,15 @@ abstract class WritableCursorTests : CursorTests() { private fun simple_WritableCursor_properties_and_invariants(endianness: Endianness) { val cursor = createCursor(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), endianness) - assertEquals(0u, cursor.position) + assertEquals(0, cursor.position) cursor.writeU8(99u).writeU8(99u).writeU8(99u).writeU8(99u) cursor.seek(-1) assertEquals(cursor.position + cursor.bytesLeft, cursor.size) - assertEquals(10u, cursor.size) - assertEquals(3u, cursor.position) - assertEquals(7u, cursor.bytesLeft) + assertEquals(10, cursor.size) + assertEquals(3, cursor.position) + assertEquals(7, cursor.bytesLeft) assertEquals(endianness, cursor.endianness) } @@ -83,9 +84,9 @@ abstract class WritableCursorTests : CursorTests() { cursor.write(expectedNumber1) cursor.write(expectedNumber2) - assertEquals((2 * byteCount).toUInt(), cursor.position) + assertEquals(2 * byteCount, cursor.position) - cursor.seekStart(0u) + cursor.seekStart(0) assertEquals(expectedNumber1, cursor.read()) assertEquals(expectedNumber2, cursor.read()) @@ -106,23 +107,23 @@ abstract class WritableCursorTests : CursorTests() { cursor.writeF32(1337.9001f) cursor.writeF32(103.502f) - assertEquals(8u, cursor.position) + assertEquals(8, cursor.position) - cursor.seekStart(0u) + cursor.seekStart(0) // The read floats won't be exactly the same as the written floats in Kotlin JS, because // they're backed by numbers (64-bit floats). assertTrue(abs(1337.9001f - cursor.f32()) < 0.001) assertTrue(abs(103.502f - cursor.f32()) < 0.001) - assertEquals(8u, cursor.position) + assertEquals(8, cursor.position) } @Test fun writeU8Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = u8Array(n) - IntArray(n.toInt()) { arr[it].toInt() } + IntArray(n) { arr[it].toInt() } } val write: WritableCursor.(IntArray) -> Unit = { a -> writeU8Array(UByteArray(a.size) { a[it].toUByte() }) @@ -134,9 +135,9 @@ abstract class WritableCursorTests : CursorTests() { @Test fun writeU16Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = u16Array(n) - IntArray(n.toInt()) { arr[it].toInt() } + IntArray(n) { arr[it].toInt() } } val write: WritableCursor.(IntArray) -> Unit = { a -> writeU16Array(UShortArray(a.size) { a[it].toUShort() }) @@ -148,9 +149,9 @@ abstract class WritableCursorTests : CursorTests() { @Test fun writeU32Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> val arr = u32Array(n) - IntArray(n.toInt()) { arr[it].toInt() } + IntArray(n) { arr[it].toInt() } } val write: WritableCursor.(IntArray) -> Unit = { a -> writeU32Array(UIntArray(a.size) { a[it].toUInt() }) @@ -162,7 +163,7 @@ abstract class WritableCursorTests : CursorTests() { @Test fun writeI32Array() { - val read: Cursor.(UInt) -> IntArray = { n -> + val read: Cursor.(Int) -> IntArray = { n -> i32Array(n) } val write: WritableCursor.(IntArray) -> Unit = { a -> @@ -175,7 +176,7 @@ abstract class WritableCursorTests : CursorTests() { private fun testIntegerArrayWrite( byteCount: Int, - read: Cursor.(UInt) -> IntArray, + read: Cursor.(Int) -> IntArray, write: WritableCursor.(IntArray) -> Unit, endianness: Endianness, ) { @@ -185,16 +186,42 @@ abstract class WritableCursorTests : CursorTests() { val cursor = createCursor(ByteArray(20 * byteCount), endianness) cursor.write(testArray1) - assertEquals(10u * byteCount.toUInt(), cursor.position) + assertEquals(10 * byteCount, cursor.position) cursor.write(testArray2) - assertEquals(20u * byteCount.toUInt(), cursor.position) + assertEquals(20 * byteCount, cursor.position) - cursor.seekStart(0u) + cursor.seekStart(0) - assertTrue(testArray1.contentEquals(cursor.read(10u))) - assertTrue(testArray2.contentEquals(cursor.read(10u))) - assertEquals(20u * byteCount.toUInt(), cursor.position) + assertTrue(testArray1.contentEquals(cursor.read(10))) + assertTrue(testArray2.contentEquals(cursor.read(10))) + assertEquals(20 * byteCount, cursor.position) + } + + @Test + fun writeCursor() { + testWriteCursor(Endianness.Little) + testWriteCursor(Endianness.Big) + } + + private fun testWriteCursor(endianness: Endianness) { + val cursor = createCursor(ByteArray(8), endianness) + + cursor.seek(2) + cursor.writeCursor(Buffer.fromByteArray(byteArrayOf(1, 2, 3, 4)).cursor()) + + assertEquals(6, cursor.position) + + cursor.seekStart(0) + + assertEquals(0, cursor.i8()) + assertEquals(0, cursor.i8()) + assertEquals(1, cursor.i8()) + assertEquals(2, cursor.i8()) + assertEquals(3, cursor.i8()) + assertEquals(4, cursor.i8()) + assertEquals(0, cursor.i8()) + assertEquals(0, cursor.i8()) } @Test @@ -208,10 +235,11 @@ abstract class WritableCursorTests : CursorTests() { cursor.writeU32(1u).writeU32(2u).writeU32(3u).writeU32(4u) cursor.seek(-8) - val newCursor = cursor.take(8u) + val newCursor = cursor.take(8) - assertEquals(8u, newCursor.size) - assertEquals(0u, newCursor.position) + assertEquals(16, cursor.position) + assertEquals(8, newCursor.size) + assertEquals(0, newCursor.position) assertEquals(3u, newCursor.u32()) assertEquals(4u, newCursor.u32()) } diff --git a/lib/src/jsMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt b/lib/src/jsMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt index 0f10f89f..69d45d0e 100644 --- a/lib/src/jsMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt +++ b/lib/src/jsMain/kotlin/world/phantasmal/lib/buffer/Buffer.kt @@ -2,19 +2,20 @@ package world.phantasmal.lib.buffer import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.DataView +import org.khronos.webgl.Int8Array import org.khronos.webgl.Uint8Array import world.phantasmal.lib.Endianness import world.phantasmal.lib.ZERO_U16 actual class Buffer private constructor( private var arrayBuffer: ArrayBuffer, - size: UInt, + size: Int, endianness: Endianness, ) { private var dataView = DataView(arrayBuffer) private var littleEndian = endianness == Endianness.Little - actual var size: UInt = size + actual var size: Int = size set(value) { ensureCapacity(value) field = value @@ -26,54 +27,54 @@ actual class Buffer private constructor( littleEndian = value == Endianness.Little } - actual val capacity: UInt - get() = arrayBuffer.byteLength.toUInt() + actual val capacity: Int + get() = arrayBuffer.byteLength - actual fun getU8(offset: UInt): UByte { - checkOffset(offset, 1u) - return dataView.getUint8(offset.toInt()).toUByte() + actual fun getU8(offset: Int): UByte { + checkOffset(offset, 1) + return dataView.getUint8(offset).toUByte() } - actual fun getU16(offset: UInt): UShort { - checkOffset(offset, 2u) - return dataView.getUint16(offset.toInt(), littleEndian).toUShort() + actual fun getU16(offset: Int): UShort { + checkOffset(offset, 2) + return dataView.getUint16(offset, littleEndian).toUShort() } - actual fun getU32(offset: UInt): UInt { - checkOffset(offset, 4u) - return dataView.getUint32(offset.toInt(), littleEndian).toUInt() + actual fun getU32(offset: Int): UInt { + checkOffset(offset, 4) + return dataView.getUint32(offset, littleEndian).toUInt() } - actual fun getI8(offset: UInt): Byte { - checkOffset(offset, 1u) - return dataView.getInt8(offset.toInt()) + actual fun getI8(offset: Int): Byte { + checkOffset(offset, 1) + return dataView.getInt8(offset) } - actual fun getI16(offset: UInt): Short { - checkOffset(offset, 2u) - return dataView.getInt16(offset.toInt(), littleEndian) + actual fun getI16(offset: Int): Short { + checkOffset(offset, 2) + return dataView.getInt16(offset, littleEndian) } - actual fun getI32(offset: UInt): Int { - checkOffset(offset, 4u) - return dataView.getInt32(offset.toInt(), littleEndian) + actual fun getI32(offset: Int): Int { + checkOffset(offset, 4) + return dataView.getInt32(offset, littleEndian) } - actual fun getF32(offset: UInt): Float { - checkOffset(offset, 4u) - return dataView.getFloat32(offset.toInt(), littleEndian) + actual fun getF32(offset: Int): Float { + checkOffset(offset, 4) + return dataView.getFloat32(offset, littleEndian) } actual fun getStringUtf16( - offset: UInt, - maxByteLength: UInt, + offset: Int, + maxByteLength: Int, nullTerminated: Boolean, ): String = buildString { - val len = maxByteLength / 2u + val len = maxByteLength / 2 - for (i in 0u until len) { - val codePoint = getU16(offset + i * 2u) + for (i in 0 until len) { + val codePoint = getU16(offset + i * 2) if (nullTerminated && codePoint == ZERO_U16) { break @@ -83,90 +84,69 @@ actual class Buffer private constructor( } } - actual fun slice(offset: UInt, size: UInt): Buffer { + actual fun slice(offset: Int, size: Int): Buffer { checkOffset(offset, size) return fromArrayBuffer( - arrayBuffer.slice(offset.toInt(), (offset + size).toInt()), + arrayBuffer.slice(offset, (offset + size)), endianness ) } - /** - * Writes an unsigned 8-bit integer at the given offset. - */ - actual fun setU8(offset: UInt, value: UByte): Buffer { - checkOffset(offset, 1u) - dataView.setUint8(offset.toInt(), value.toByte()) + actual fun setU8(offset: Int, value: UByte): Buffer { + checkOffset(offset, 1) + dataView.setUint8(offset, value.toByte()) return this } - /** - * Writes an unsigned 16-bit integer at the given offset. - */ - actual fun setU16(offset: UInt, value: UShort): Buffer { - checkOffset(offset, 2u) - dataView.setUint16(offset.toInt(), value.toShort(), littleEndian) + actual fun setU16(offset: Int, value: UShort): Buffer { + checkOffset(offset, 2) + dataView.setUint16(offset, value.toShort(), littleEndian) return this } - /** - * Writes an unsigned 32-bit integer at the given offset. - */ - actual fun setU32(offset: UInt, value: UInt): Buffer { - checkOffset(offset, 4u) - dataView.setUint32(offset.toInt(), value.toInt(), littleEndian) + actual fun setU32(offset: Int, value: UInt): Buffer { + checkOffset(offset, 4) + dataView.setUint32(offset, value.toInt(), littleEndian) return this } - /** - * Writes a signed 8-bit integer at the given offset. - */ - actual fun setI8(offset: UInt, value: Byte): Buffer { - checkOffset(offset, 1u) - dataView.setInt8(offset.toInt(), value) + actual fun setI8(offset: Int, value: Byte): Buffer { + checkOffset(offset, 1) + dataView.setInt8(offset, value) return this } - /** - * Writes a signed 16-bit integer at the given offset. - */ - actual fun setI16(offset: UInt, value: Short): Buffer { - checkOffset(offset, 2u) - dataView.setInt16(offset.toInt(), value, littleEndian) + actual fun setI16(offset: Int, value: Short): Buffer { + checkOffset(offset, 2) + dataView.setInt16(offset, value, littleEndian) return this } - /** - * Writes a signed 32-bit integer at the given offset. - */ - actual fun setI32(offset: UInt, value: Int): Buffer { - checkOffset(offset, 4u) - dataView.setInt32(offset.toInt(), value, littleEndian) + actual fun setI32(offset: Int, value: Int): Buffer { + checkOffset(offset, 4) + dataView.setInt32(offset, value, littleEndian) return this } - /** - * Writes a 32-bit floating point number at the given offset. - */ - actual fun setF32(offset: UInt, value: Float): Buffer { - checkOffset(offset, 4u) - dataView.setFloat32(offset.toInt(), value, littleEndian) + actual fun setF32(offset: Int, value: Float): Buffer { + checkOffset(offset, 4) + dataView.setFloat32(offset, value, littleEndian) return this } - /** - * Writes 0 bytes to the entire buffer. - */ - actual fun zero(): Buffer { - (Uint8Array(arrayBuffer).asDynamic()).fill(0) + actual fun zero(): Buffer = + fill(0) + + actual fun fill(value: Byte): Buffer { + (Int8Array(arrayBuffer).asDynamic()).fill(value) return this } /** * Checks whether we can read [size] bytes at [offset]. */ - private fun checkOffset(offset: UInt, size: UInt) { - require(offset + size <= this.size) { + private fun checkOffset(offset: Int, size: Int) { + require(offset >= 0 && offset + size <= this.size) { "Offset $offset is out of bounds." } } @@ -174,16 +154,16 @@ actual class Buffer private constructor( /** * Reallocates the underlying ArrayBuffer if necessary. */ - private fun ensureCapacity(minNewSize: UInt) { + private fun ensureCapacity(minNewSize: Int) { if (minNewSize > capacity) { - var newSize = if (capacity == 0u) minNewSize else capacity; + var newSize = if (capacity == 0) minNewSize else capacity; do { - newSize *= 2u; + newSize *= 2; } while (newSize < minNewSize); - val newBuffer = ArrayBuffer(newSize.toInt()); - Uint8Array(newBuffer).set(Uint8Array(arrayBuffer, 0, size.toInt())); + val newBuffer = ArrayBuffer(newSize); + Uint8Array(newBuffer).set(Uint8Array(arrayBuffer, 0, size)); arrayBuffer = newBuffer; dataView = DataView(arrayBuffer); } @@ -191,19 +171,21 @@ actual class Buffer private constructor( actual companion object { actual fun withCapacity( - initialCapacity: UInt, + initialCapacity: Int, endianness: Endianness, ): Buffer = - Buffer(ArrayBuffer(initialCapacity.toInt()), size = 0u, endianness) + Buffer(ArrayBuffer(initialCapacity), size = 0, endianness) + + actual fun withSize(initialSize: Int, endianness: Endianness): Buffer = + Buffer(ArrayBuffer(initialSize), initialSize, endianness) actual fun fromByteArray(array: ByteArray, endianness: Endianness): Buffer { val arrayBuffer = ArrayBuffer(array.size) - Uint8Array(arrayBuffer).set(array.toTypedArray()) - return Buffer(arrayBuffer, array.size.toUInt(), endianness) + Int8Array(arrayBuffer).set(array.toTypedArray()) + return Buffer(arrayBuffer, array.size, endianness) } - fun fromArrayBuffer(arrayBuffer: ArrayBuffer, endianness: Endianness): Buffer { - return Buffer(arrayBuffer, arrayBuffer.byteLength.toUInt(), endianness) - } + fun fromArrayBuffer(arrayBuffer: ArrayBuffer, endianness: Endianness): Buffer = + Buffer(arrayBuffer, arrayBuffer.byteLength, endianness) } } diff --git a/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/AbstractArrayBufferCursor.kt b/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/AbstractArrayBufferCursor.kt deleted file mode 100644 index d44e0ea0..00000000 --- a/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/AbstractArrayBufferCursor.kt +++ /dev/null @@ -1,179 +0,0 @@ -package world.phantasmal.lib.cursor - -import org.khronos.webgl.ArrayBuffer -import org.khronos.webgl.DataView -import world.phantasmal.lib.Endianness -import world.phantasmal.lib.buffer.Buffer - -abstract class AbstractArrayBufferCursor -protected constructor(endianness: Endianness, offset: UInt) : AbstractWritableCursor(offset) { - private var littleEndian: Boolean = endianness == Endianness.Little - protected abstract val backingBuffer: ArrayBuffer - protected abstract val dv: DataView - - override var endianness: Endianness - get() = if (littleEndian) Endianness.Little else Endianness.Big - set(value) { - littleEndian = value == Endianness.Little - } - - override fun u8(): UByte { - requireSize(1u) - val r = dv.getUint8(absolutePosition.toInt()) - position++ - return r.toUByte() - } - - override fun u16(): UShort { - requireSize(2u) - val r = dv.getUint16(absolutePosition.toInt(), littleEndian) - position += 2u - return r.toUShort() - } - - override fun u32(): UInt { - requireSize(4u) - val r = dv.getUint32(absolutePosition.toInt(), littleEndian) - position += 4u - return r.toUInt() - } - - override fun i8(): Byte { - requireSize(1u) - val r = dv.getInt8(absolutePosition.toInt()) - position++ - return r - } - - override fun i16(): Short { - requireSize(2u) - val r = dv.getInt16(absolutePosition.toInt(), littleEndian) - position += 2u - return r - } - - override fun i32(): Int { - requireSize(4u) - val r = dv.getInt32(absolutePosition.toInt(), littleEndian) - position += 4u - return r - } - - override fun f32(): Float { - requireSize(4u) - val r = dv.getFloat32(absolutePosition.toInt(), littleEndian) - position += 4u - return r - } - - override fun u8Array(n: UInt): UByteArray { - requireSize(n) - - val array = UByteArray(n.toInt()) - - for (i in 0 until n.toInt()) { - array[i] = dv.getUint8(absolutePosition.toInt()).toUByte() - position++ - } - - return array - } - - override fun u16Array(n: UInt): UShortArray { - requireSize(2u * n) - - val array = UShortArray(n.toInt()) - - for (i in 0 until n.toInt()) { - array[i] = dv.getUint16(absolutePosition.toInt(), littleEndian).toUShort() - position += 2u - } - - return array - } - - override fun u32Array(n: UInt): UIntArray { - requireSize(4u * n) - - val array = UIntArray(n.toInt()) - - for (i in 0 until n.toInt()) { - array[i] = dv.getUint32(absolutePosition.toInt(), littleEndian).toUInt() - position += 4u - } - - return array - } - - override fun i32Array(n: UInt): IntArray { - requireSize(4u * n) - - val array = IntArray(n.toInt()) - - for (i in 0 until n.toInt()) { - array[i] = dv.getInt32(absolutePosition.toInt(), littleEndian) - position += 4u - } - - return array - } - - override fun buffer(size: UInt): Buffer { - requireSize(size) - val r = Buffer.fromArrayBuffer( - backingBuffer.slice(absolutePosition.toInt(), (absolutePosition + size).toInt()), - endianness - ) - position += size - return r - } - - override fun writeU8(value: UByte): WritableCursor { - requireSize(1u) - dv.setUint8(absolutePosition.toInt(), value.toByte()) - position++ - return this - } - - override fun writeU16(value: UShort): WritableCursor { - requireSize(2u) - dv.setUint16(absolutePosition.toInt(), value.toShort(), littleEndian) - position += 2u - return this - } - - override fun writeU32(value: UInt): WritableCursor { - requireSize(4u) - dv.setUint32(absolutePosition.toInt(), value.toInt(), littleEndian) - position += 4u - return this - } - - override fun writeI8(value: Byte): WritableCursor { - requireSize(1u) - dv.setInt8(absolutePosition.toInt(), value) - position++ - return this - } - - override fun writeI16(value: Short): WritableCursor { - requireSize(2u) - dv.setInt16(absolutePosition.toInt(), value, littleEndian) - position += 2u - return this - } - - override fun writeI32(value: Int): WritableCursor { - requireSize(4u) - dv.setInt32(absolutePosition.toInt(), value, littleEndian) - position += 4u - return this - } - - override fun writeF32(value: Float): WritableCursor { - requireSize(4u) - dv.setFloat32(absolutePosition.toInt(), value, littleEndian) - position += 4u - return this - } -} diff --git a/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/ArrayBufferCursor.kt b/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/ArrayBufferCursor.kt index 1b02be50..cb37d332 100644 --- a/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/ArrayBufferCursor.kt +++ b/lib/src/jsMain/kotlin/world/phantasmal/lib/cursor/ArrayBufferCursor.kt @@ -3,6 +3,7 @@ package world.phantasmal.lib.cursor import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.DataView import world.phantasmal.lib.Endianness +import world.phantasmal.lib.buffer.Buffer /** * A cursor for reading from an array buffer or part of an array buffer. @@ -15,22 +16,192 @@ import world.phantasmal.lib.Endianness class ArrayBufferCursor( buffer: ArrayBuffer, endianness: Endianness, - offset: UInt = 0u, - size: UInt = buffer.byteLength.toUInt() - offset, -) : AbstractArrayBufferCursor(endianness, offset) { - override val backingBuffer = buffer - override val dv = DataView(buffer, 0, buffer.byteLength) + offset: Int = 0, + size: Int = buffer.byteLength - offset, +) : AbstractWritableCursor(offset) { + private var littleEndian: Boolean = endianness == Endianness.Little + private val backingBuffer = buffer + private val dv = DataView(buffer) - override var size: UInt = size + override var size: Int = size set(value) { - require(size <= backingBuffer.byteLength.toUInt() - offset) + require(size <= backingBuffer.byteLength - offset) field = value } - override fun take(size: UInt): ArrayBufferCursor { + override var endianness: Endianness + get() = if (littleEndian) Endianness.Little else Endianness.Big + set(value) { + littleEndian = value == Endianness.Little + } + + override fun u8(): UByte { + requireSize(1) + val r = dv.getUint8(absolutePosition) + position++ + return r.toUByte() + } + + override fun u16(): UShort { + requireSize(2) + val r = dv.getUint16(absolutePosition, littleEndian) + position += 2 + return r.toUShort() + } + + override fun u32(): UInt { + requireSize(4) + val r = dv.getUint32(absolutePosition, littleEndian) + position += 4 + return r.toUInt() + } + + override fun i8(): Byte { + requireSize(1) + val r = dv.getInt8(absolutePosition) + position++ + return r + } + + override fun i16(): Short { + requireSize(2) + val r = dv.getInt16(absolutePosition, littleEndian) + position += 2 + return r + } + + override fun i32(): Int { + requireSize(4) + val r = dv.getInt32(absolutePosition, littleEndian) + position += 4 + return r + } + + override fun f32(): Float { + requireSize(4) + val r = dv.getFloat32(absolutePosition, littleEndian) + position += 4 + return r + } + + override fun u8Array(n: Int): UByteArray { + requireSize(n) + + val array = UByteArray(n) + + for (i in 0 until n) { + array[i] = dv.getUint8(absolutePosition).toUByte() + position++ + } + + return array + } + + override fun u16Array(n: Int): UShortArray { + requireSize(2 * n) + + val array = UShortArray(n) + + for (i in 0 until n) { + array[i] = dv.getUint16(absolutePosition, littleEndian).toUShort() + position += 2 + } + + return array + } + + override fun u32Array(n: Int): UIntArray { + requireSize(4 * n) + + val array = UIntArray(n) + + for (i in 0 until n) { + array[i] = dv.getUint32(absolutePosition, littleEndian).toUInt() + position += 4 + } + + return array + } + + override fun i32Array(n: Int): IntArray { + requireSize(4 * n) + + val array = IntArray(n) + + for (i in 0 until n) { + array[i] = dv.getInt32(absolutePosition, littleEndian) + position += 4 + } + + return array + } + + override fun take(size: Int): Cursor { val offset = offset + position val wrapper = ArrayBufferCursor(backingBuffer, endianness, offset, size) this.position += size return wrapper } + + override fun buffer(size: Int): Buffer { + requireSize(size) + val r = Buffer.fromArrayBuffer( + backingBuffer.slice(absolutePosition, (absolutePosition + size)), + endianness + ) + position += size + return r + } + + override fun writeU8(value: UByte): WritableCursor { + requireSize(1) + dv.setUint8(absolutePosition, value.toByte()) + position++ + return this + } + + override fun writeU16(value: UShort): WritableCursor { + requireSize(2) + dv.setUint16(absolutePosition, value.toShort(), littleEndian) + position += 2 + return this + } + + override fun writeU32(value: UInt): WritableCursor { + requireSize(4) + dv.setUint32(absolutePosition, value.toInt(), littleEndian) + position += 4 + return this + } + + override fun writeI8(value: Byte): WritableCursor { + requireSize(1) + dv.setInt8(absolutePosition, value) + position++ + return this + } + + override fun writeI16(value: Short): WritableCursor { + requireSize(2) + dv.setInt16(absolutePosition, value, littleEndian) + position += 2 + return this + } + + override fun writeI32(value: Int): WritableCursor { + requireSize(4) + dv.setInt32(absolutePosition, value, littleEndian) + position += 4 + return this + } + + override fun writeF32(value: Float): WritableCursor { + requireSize(4) + dv.setFloat32(absolutePosition, value, littleEndian) + position += 4 + return this + } } + +fun ArrayBuffer.cursor(endianness: Endianness): ArrayBufferCursor = + ArrayBufferCursor(this, endianness) diff --git a/web/src/main/kotlin/world/phantasmal/web/Main.kt b/web/src/main/kotlin/world/phantasmal/web/Main.kt index 70011768..087788fd 100644 --- a/web/src/main/kotlin/world/phantasmal/web/Main.kt +++ b/web/src/main/kotlin/world/phantasmal/web/Main.kt @@ -6,7 +6,7 @@ import io.ktor.client.features.json.serializer.* import kotlinx.browser.document import kotlinx.browser.window import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import org.w3c.dom.PopStateEvent import world.phantasmal.core.disposable.Disposable @@ -47,7 +47,7 @@ private fun init(): Disposable { val basePath = window.location.origin + (if (pathname.lastOrNull() == '/') pathname.dropLast(1) else pathname) - val scope = CoroutineScope(Job()) + val scope = CoroutineScope(SupervisorJob()) disposer.add(disposable { scope.cancel() }) disposer.add( diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/QuestEditorToolbarController.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/QuestEditorToolbarController.kt index 1bed7d77..651e87d1 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/QuestEditorToolbarController.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/QuestEditorToolbarController.kt @@ -5,7 +5,7 @@ import kotlinx.coroutines.launch import org.w3c.files.File import world.phantasmal.core.* import world.phantasmal.lib.Endianness -import world.phantasmal.lib.cursor.ArrayBufferCursor +import world.phantasmal.lib.cursor.cursor import world.phantasmal.lib.fileFormats.quest.Quest import world.phantasmal.lib.fileFormats.quest.parseBinDatToQuest import world.phantasmal.observable.value.Val @@ -17,7 +17,7 @@ import world.phantasmal.webui.readFile class QuestEditorToolbarController( scope: CoroutineScope, - private val questEditorStore: QuestEditorStore + private val questEditorStore: QuestEditorStore, ) : Controller(scope) { private val _resultDialogVisible = mutableVal(false) private val _result = mutableVal?>(null) @@ -46,11 +46,9 @@ class QuestEditorToolbarController( return@launch } - val binBuffer = readFile(bin) - val datBuffer = readFile(dat) val parseResult = parseBinDatToQuest( - ArrayBufferCursor(binBuffer, Endianness.Little), - ArrayBufferCursor(datBuffer, Endianness.Little) + readFile(bin).cursor(Endianness.Little), + readFile(dat).cursor(Endianness.Little), ) setResult(parseResult)