mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-07 08:48:28 +08:00
Added PRS compression code and fixed several bugs and performance issues.
This commit is contained in:
parent
b810e45fb3
commit
3416ef5676
@ -177,10 +177,10 @@ private class Assembler(private val assembly: List<String>, private val manualSt
|
|||||||
|
|
||||||
is DataSegment -> {
|
is DataSegment -> {
|
||||||
val oldSize = seg.data.size
|
val oldSize = seg.data.size
|
||||||
seg.data.size += bytes.size.toUInt()
|
seg.data.size += bytes.size
|
||||||
|
|
||||||
for (i in bytes.indices) {
|
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<String>, private val manualSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addUnexpectedTokenError(token: Token) {
|
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) {
|
private fun addWarning(token: Token, uiMessage: String) {
|
||||||
@ -288,7 +290,7 @@ private class Assembler(private val assembly: List<String>, private val manualSt
|
|||||||
if (!prevLineHadLabel) {
|
if (!prevLineHadLabel) {
|
||||||
segment = DataSegment(
|
segment = DataSegment(
|
||||||
labels = mutableListOf(label),
|
labels = mutableListOf(label),
|
||||||
data = Buffer.withCapacity(0u),
|
data = Buffer.withCapacity(0),
|
||||||
srcLoc = SegmentSrcLoc(labels = mutableListOf(srcLoc)),
|
srcLoc = SegmentSrcLoc(labels = mutableListOf(srcLoc)),
|
||||||
)
|
)
|
||||||
objectCode.add(segment!!)
|
objectCode.add(segment!!)
|
||||||
|
@ -6,102 +6,109 @@ import world.phantasmal.lib.Endianness
|
|||||||
* Resizable, continuous block of bytes which is reallocated when necessary.
|
* Resizable, continuous block of bytes which is reallocated when necessary.
|
||||||
*/
|
*/
|
||||||
expect class Buffer {
|
expect class Buffer {
|
||||||
var size: UInt
|
var size: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Byte order mode.
|
* Byte order mode.
|
||||||
*/
|
*/
|
||||||
var endianness: Endianness
|
var endianness: Endianness
|
||||||
|
|
||||||
val capacity: UInt
|
val capacity: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an unsigned 8-bit integer at the given offset.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* Writes 0 bytes to the entire buffer.
|
||||||
*/
|
*/
|
||||||
fun zero(): Buffer
|
fun zero(): Buffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes [value] to every byte in the buffer.
|
||||||
|
*/
|
||||||
|
fun fill(value: Byte): Buffer
|
||||||
|
|
||||||
companion object {
|
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
|
fun fromByteArray(array: ByteArray, endianness: Endianness = Endianness.Little): Buffer
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,9 +6,9 @@ import world.phantasmal.core.PwResultBuilder
|
|||||||
import world.phantasmal.core.Severity
|
import world.phantasmal.core.Severity
|
||||||
import world.phantasmal.core.Success
|
import world.phantasmal.core.Success
|
||||||
import world.phantasmal.lib.buffer.Buffer
|
import world.phantasmal.lib.buffer.Buffer
|
||||||
import world.phantasmal.lib.cursor.BufferCursor
|
|
||||||
import world.phantasmal.lib.cursor.Cursor
|
import world.phantasmal.lib.cursor.Cursor
|
||||||
import world.phantasmal.lib.cursor.WritableCursor
|
import world.phantasmal.lib.cursor.WritableCursor
|
||||||
|
import world.phantasmal.lib.cursor.cursor
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@ -16,26 +16,27 @@ private val logger = KotlinLogging.logger {}
|
|||||||
|
|
||||||
fun prsDecompress(cursor: Cursor): PwResult<Cursor> {
|
fun prsDecompress(cursor: Cursor): PwResult<Cursor> {
|
||||||
try {
|
try {
|
||||||
val ctx = Context(cursor)
|
val decompressor = PrsDecompressor(cursor)
|
||||||
|
var i = 0
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (ctx.readFlagBit() == 1u) {
|
if (decompressor.readFlagBit() == 1) {
|
||||||
// Single byte copy.
|
// Single byte copy.
|
||||||
ctx.copyU8()
|
decompressor.copyU8()
|
||||||
} else {
|
} else {
|
||||||
// Multi byte copy.
|
// Multi byte copy.
|
||||||
var length: UInt
|
var length: Int
|
||||||
var offset: Int
|
var offset: Int
|
||||||
|
|
||||||
if (ctx.readFlagBit() == 0u) {
|
if (decompressor.readFlagBit() == 0) {
|
||||||
// Short copy.
|
// Short copy.
|
||||||
length = (ctx.readFlagBit() shl 1) or ctx.readFlagBit()
|
length = (decompressor.readFlagBit() shl 1) or decompressor.readFlagBit()
|
||||||
length += 2u
|
length += 2
|
||||||
|
|
||||||
offset = ctx.readU8().toInt() - 256
|
offset = decompressor.readU8().toInt() - 256
|
||||||
} else {
|
} else {
|
||||||
// Long copy or end of file.
|
// 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.
|
// Two zero bytes implies that this is the end of the file.
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
@ -43,24 +44,26 @@ fun prsDecompress(cursor: Cursor): PwResult<Cursor> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to read a length byte, or is it encoded in what we already have?
|
// Do we need to read a length byte, or is it encoded in what we already have?
|
||||||
length = (offset and 0b111).toUInt()
|
length = offset and 0b111
|
||||||
offset = offset shr 3
|
offset = offset ushr 3
|
||||||
|
|
||||||
if (length == 0u) {
|
if (length == 0) {
|
||||||
length = ctx.readU8().toUInt()
|
length = decompressor.readU8().toInt()
|
||||||
length += 1u
|
length += 1
|
||||||
} else {
|
} else {
|
||||||
length += 2u
|
length += 2
|
||||||
}
|
}
|
||||||
|
|
||||||
offset -= 8192
|
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) {
|
} catch (e: Throwable) {
|
||||||
return PwResultBuilder<Cursor>(logger)
|
return PwResultBuilder<Cursor>(logger)
|
||||||
.addProblem(Severity.Error, "PRS-compressed stream is corrupt.", cause = e)
|
.addProblem(Severity.Error, "PRS-compressed stream is corrupt.", cause = e)
|
||||||
@ -68,23 +71,22 @@ fun prsDecompress(cursor: Cursor): PwResult<Cursor> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Context(cursor: Cursor) {
|
private class PrsDecompressor(cursor: Cursor) {
|
||||||
private val src: Cursor = cursor
|
private val src: Cursor = cursor
|
||||||
val dst: WritableCursor = BufferCursor(
|
val dst: WritableCursor =
|
||||||
Buffer.withCapacity(floor(1.5 * cursor.size.toDouble()).toUInt(), cursor.endianness),
|
Buffer.withCapacity(floor(1.5 * cursor.size.toDouble()).toInt(), cursor.endianness).cursor()
|
||||||
)
|
private var flags = 0
|
||||||
private var flags = 0u
|
|
||||||
private var flagBitsLeft = 0
|
private var flagBitsLeft = 0
|
||||||
|
|
||||||
fun readFlagBit(): UInt {
|
fun readFlagBit(): Int {
|
||||||
// Fetch a new flag byte when the previous byte has been processed.
|
// Fetch a new flag byte when the previous byte has been processed.
|
||||||
if (flagBitsLeft == 0) {
|
if (flagBitsLeft == 0) {
|
||||||
flags = readU8().toUInt()
|
flags = readU8().toInt()
|
||||||
flagBitsLeft = 8
|
flagBitsLeft = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
val bit = flags and 1u
|
val bit = flags and 1
|
||||||
flags = flags shr 1
|
flags = flags ushr 1
|
||||||
flagBitsLeft -= 1
|
flagBitsLeft -= 1
|
||||||
return bit
|
return bit
|
||||||
}
|
}
|
||||||
@ -97,25 +99,26 @@ class Context(cursor: Cursor) {
|
|||||||
|
|
||||||
fun readU16(): UShort = src.u16()
|
fun readU16(): UShort = src.u16()
|
||||||
|
|
||||||
fun offsetCopy(offset: Int, length: UInt) {
|
fun offsetCopy(offset: Int, length: Int) {
|
||||||
require(offset in -8192..0) {
|
require(offset in -8192..0) {
|
||||||
"offset was ${offset}, should be between -8192 and 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."
|
"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.
|
// The length can be larger than -offset, in that case we copy -offset bytes size/-offset
|
||||||
val bufSize = min((-offset).toUInt(), length)
|
// times.
|
||||||
|
val bufSize = min(-offset, length)
|
||||||
|
|
||||||
dst.seek(offset)
|
dst.seek(offset)
|
||||||
val buf = dst.take(bufSize)
|
val buf = dst.take(bufSize)
|
||||||
dst.seek(-offset - bufSize.toInt())
|
dst.seek(-offset - bufSize)
|
||||||
|
|
||||||
repeat((length / bufSize).toInt()) {
|
repeat(length / bufSize) {
|
||||||
dst.writeCursor(buf)
|
dst.writeCursor(buf)
|
||||||
buf.seekStart(0u)
|
buf.seekStart(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.writeCursor(buf.take(length % bufSize))
|
dst.writeCursor(buf.take(length % bufSize))
|
||||||
|
@ -2,52 +2,51 @@ package world.phantasmal.lib.cursor
|
|||||||
|
|
||||||
import world.phantasmal.lib.ZERO_U16
|
import world.phantasmal.lib.ZERO_U16
|
||||||
import world.phantasmal.lib.ZERO_U8
|
import world.phantasmal.lib.ZERO_U8
|
||||||
import world.phantasmal.lib.buffer.Buffer
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
abstract class AbstractWritableCursor
|
abstract class AbstractWritableCursor
|
||||||
protected constructor(protected val offset: UInt) : WritableCursor {
|
protected constructor(protected val offset: Int) : WritableCursor {
|
||||||
override var position: UInt = 0u
|
override var position: Int = 0
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
override val bytesLeft: UInt
|
override val bytesLeft: Int
|
||||||
get() = size - position
|
get() = size - position
|
||||||
|
|
||||||
protected val absolutePosition: UInt
|
protected val absolutePosition: Int
|
||||||
get() = offset + position
|
get() = offset + position
|
||||||
|
|
||||||
override fun hasBytesLeft(bytes: UInt): Boolean =
|
override fun hasBytesLeft(bytes: Int): Boolean =
|
||||||
bytesLeft >= bytes
|
bytesLeft >= bytes
|
||||||
|
|
||||||
override fun seek(offset: Int): WritableCursor =
|
override fun seek(offset: Int): WritableCursor =
|
||||||
seekStart((position.toInt() + offset).toUInt())
|
seekStart(position + offset)
|
||||||
|
|
||||||
override fun seekStart(offset: UInt): WritableCursor {
|
override fun seekStart(offset: Int): WritableCursor {
|
||||||
require(offset <= size) { "Offset $offset is out of bounds." }
|
require(offset >= 0 || offset <= size) { "Offset $offset is out of bounds." }
|
||||||
|
|
||||||
position = offset
|
position = offset
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun seekEnd(offset: UInt): WritableCursor {
|
override fun seekEnd(offset: Int): WritableCursor {
|
||||||
require(offset <= size) { "Offset $offset is out of bounds." }
|
require(offset >= 0 || offset <= size) { "Offset $offset is out of bounds." }
|
||||||
|
|
||||||
position = size - offset
|
position = size - offset
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stringAscii(
|
override fun stringAscii(
|
||||||
maxByteLength: UInt,
|
maxByteLength: Int,
|
||||||
nullTerminated: Boolean,
|
nullTerminated: Boolean,
|
||||||
dropRemaining: Boolean,
|
dropRemaining: Boolean,
|
||||||
): String =
|
): String =
|
||||||
buildString {
|
buildString {
|
||||||
for (i in 0u until maxByteLength) {
|
for (i in 0 until maxByteLength) {
|
||||||
val codePoint = u8()
|
val codePoint = u8()
|
||||||
|
|
||||||
if (nullTerminated && codePoint == ZERO_U8) {
|
if (nullTerminated && codePoint == ZERO_U8) {
|
||||||
if (dropRemaining) {
|
if (dropRemaining) {
|
||||||
seek((maxByteLength - i - 1u).toInt())
|
seek(maxByteLength - i - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -58,19 +57,19 @@ protected constructor(protected val offset: UInt) : WritableCursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun stringUtf16(
|
override fun stringUtf16(
|
||||||
maxByteLength: UInt,
|
maxByteLength: Int,
|
||||||
nullTerminated: Boolean,
|
nullTerminated: Boolean,
|
||||||
dropRemaining: Boolean,
|
dropRemaining: Boolean,
|
||||||
): String =
|
): String =
|
||||||
buildString {
|
buildString {
|
||||||
val len = maxByteLength / 2u
|
val len = maxByteLength / 2
|
||||||
|
|
||||||
for (i in 0u until len) {
|
for (i in 0 until len) {
|
||||||
val codePoint = u16()
|
val codePoint = u16()
|
||||||
|
|
||||||
if (nullTerminated && codePoint == ZERO_U16) {
|
if (nullTerminated && codePoint == ZERO_U16) {
|
||||||
if (dropRemaining) {
|
if (dropRemaining) {
|
||||||
seek((maxByteLength - 2u * i - 2u).toInt())
|
seek(maxByteLength - 2 * i - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -82,7 +81,7 @@ protected constructor(protected val offset: UInt) : WritableCursor {
|
|||||||
|
|
||||||
override fun writeU8Array(array: UByteArray): WritableCursor {
|
override fun writeU8Array(array: UByteArray): WritableCursor {
|
||||||
val len = array.size
|
val len = array.size
|
||||||
requireSize(len.toUInt())
|
requireSize(len)
|
||||||
|
|
||||||
for (i in 0 until len) {
|
for (i in 0 until len) {
|
||||||
writeU8(array[i])
|
writeU8(array[i])
|
||||||
@ -93,7 +92,7 @@ protected constructor(protected val offset: UInt) : WritableCursor {
|
|||||||
|
|
||||||
override fun writeU16Array(array: UShortArray): WritableCursor {
|
override fun writeU16Array(array: UShortArray): WritableCursor {
|
||||||
val len = array.size
|
val len = array.size
|
||||||
requireSize(2u * len.toUInt())
|
requireSize(2 * len)
|
||||||
|
|
||||||
for (i in 0 until len) {
|
for (i in 0 until len) {
|
||||||
writeU16(array[i])
|
writeU16(array[i])
|
||||||
@ -104,7 +103,7 @@ protected constructor(protected val offset: UInt) : WritableCursor {
|
|||||||
|
|
||||||
override fun writeU32Array(array: UIntArray): WritableCursor {
|
override fun writeU32Array(array: UIntArray): WritableCursor {
|
||||||
val len = array.size
|
val len = array.size
|
||||||
requireSize(4u * len.toUInt())
|
requireSize(4 * len)
|
||||||
|
|
||||||
for (i in 0 until len) {
|
for (i in 0 until len) {
|
||||||
writeU32(array[i])
|
writeU32(array[i])
|
||||||
@ -115,7 +114,7 @@ protected constructor(protected val offset: UInt) : WritableCursor {
|
|||||||
|
|
||||||
override fun writeI32Array(array: IntArray): WritableCursor {
|
override fun writeI32Array(array: IntArray): WritableCursor {
|
||||||
val len = array.size
|
val len = array.size
|
||||||
requireSize(4u * len.toUInt())
|
requireSize(4 * len)
|
||||||
|
|
||||||
for (i in 0 until len) {
|
for (i in 0 until len) {
|
||||||
writeI32(array[i])
|
writeI32(array[i])
|
||||||
@ -127,51 +126,45 @@ protected constructor(protected val offset: UInt) : WritableCursor {
|
|||||||
override fun writeCursor(other: Cursor): WritableCursor {
|
override fun writeCursor(other: Cursor): WritableCursor {
|
||||||
val size = other.bytesLeft
|
val size = other.bytesLeft
|
||||||
requireSize(size)
|
requireSize(size)
|
||||||
|
for (i in 0 until size) {
|
||||||
for (i in 0u until (size / 4u)) {
|
writeI8(other.i8())
|
||||||
writeU32(other.u32())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0u until (size % 4u)) {
|
|
||||||
writeU8(other.u8())
|
|
||||||
}
|
|
||||||
|
|
||||||
position += size
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeStringAscii(str: String, byteLength: UInt): WritableCursor {
|
override fun writeStringAscii(str: String, byteLength: Int): WritableCursor {
|
||||||
requireSize(byteLength)
|
requireSize(byteLength)
|
||||||
|
|
||||||
val len = min(byteLength.toInt(), str.length)
|
val len = min(byteLength, str.length)
|
||||||
|
|
||||||
for (i in 0 until len) {
|
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) {
|
for (i in 0 until padLen) {
|
||||||
writeU8(0u)
|
writeI8(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeStringUtf16(str: String, byteLength: UInt): WritableCursor {
|
override fun writeStringUtf16(str: String, byteLength: Int): WritableCursor {
|
||||||
requireSize(byteLength)
|
requireSize(byteLength)
|
||||||
|
|
||||||
val maxLen = byteLength.toInt() / 2
|
val maxLen = byteLength / 2
|
||||||
val len = min(maxLen, str.length)
|
val len = min(maxLen, str.length)
|
||||||
|
|
||||||
for (i in 0 until len) {
|
for (i in 0 until len) {
|
||||||
writeU16(str[i].toShort().toUShort())
|
writeI16(str[i].toShort())
|
||||||
}
|
}
|
||||||
|
|
||||||
val padLen = maxLen - len
|
val padLen = maxLen - len
|
||||||
|
|
||||||
for (i in 0 until padLen) {
|
for (i in 0 until padLen) {
|
||||||
writeU16(0u)
|
writeI16(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
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].
|
* 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
|
val left = this.size - position
|
||||||
|
|
||||||
require(size <= left) { "$size Bytes required but only $left available." }
|
require(size <= left) { "$size Bytes required but only $left available." }
|
||||||
|
@ -10,12 +10,12 @@ import world.phantasmal.lib.buffer.Buffer
|
|||||||
*/
|
*/
|
||||||
class BufferCursor(
|
class BufferCursor(
|
||||||
private val buffer: Buffer,
|
private val buffer: Buffer,
|
||||||
offset: UInt = 0u,
|
offset: Int = 0,
|
||||||
size: UInt = buffer.size - offset,
|
size: Int = buffer.size - offset,
|
||||||
) : AbstractWritableCursor(offset) {
|
) : AbstractWritableCursor(offset) {
|
||||||
private var _size = size
|
private var _size = size
|
||||||
|
|
||||||
override var size: UInt
|
override var size: Int
|
||||||
get() = _size
|
get() = _size
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value > _size) {
|
if (value > _size) {
|
||||||
@ -52,13 +52,13 @@ class BufferCursor(
|
|||||||
|
|
||||||
override fun u16(): UShort {
|
override fun u16(): UShort {
|
||||||
val r = buffer.getU16(absolutePosition)
|
val r = buffer.getU16(absolutePosition)
|
||||||
position += 2u
|
position += 2
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun u32(): UInt {
|
override fun u32(): UInt {
|
||||||
val r = buffer.getU32(absolutePosition)
|
val r = buffer.getU32(absolutePosition)
|
||||||
position += 4u
|
position += 4
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,28 +70,28 @@ class BufferCursor(
|
|||||||
|
|
||||||
override fun i16(): Short {
|
override fun i16(): Short {
|
||||||
val r = buffer.getI16(absolutePosition)
|
val r = buffer.getI16(absolutePosition)
|
||||||
position += 2u
|
position += 2
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun i32(): Int {
|
override fun i32(): Int {
|
||||||
val r = buffer.getI32(absolutePosition)
|
val r = buffer.getI32(absolutePosition)
|
||||||
position += 4u
|
position += 4
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun f32(): Float {
|
override fun f32(): Float {
|
||||||
val r = buffer.getF32(absolutePosition)
|
val r = buffer.getF32(absolutePosition)
|
||||||
position += 4u
|
position += 4
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun u8Array(n: UInt): UByteArray {
|
override fun u8Array(n: Int): UByteArray {
|
||||||
requireSize(n)
|
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)
|
array[i] = buffer.getU8(absolutePosition)
|
||||||
position++
|
position++
|
||||||
}
|
}
|
||||||
@ -99,123 +99,123 @@ class BufferCursor(
|
|||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun u16Array(n: UInt): UShortArray {
|
override fun u16Array(n: Int): UShortArray {
|
||||||
requireSize(2u * n)
|
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)
|
array[i] = buffer.getU16(absolutePosition)
|
||||||
position += 2u
|
position += 2
|
||||||
}
|
}
|
||||||
|
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun u32Array(n: UInt): UIntArray {
|
override fun u32Array(n: Int): UIntArray {
|
||||||
requireSize(4u * n)
|
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)
|
array[i] = buffer.getU32(absolutePosition)
|
||||||
position += 4u
|
position += 4
|
||||||
}
|
}
|
||||||
|
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun i32Array(n: UInt): IntArray {
|
override fun i32Array(n: Int): IntArray {
|
||||||
requireSize(4u * n)
|
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)
|
array[i] = buffer.getI32(absolutePosition)
|
||||||
position += 4u
|
position += 4
|
||||||
}
|
}
|
||||||
|
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun take(size: UInt): Cursor {
|
override fun take(size: Int): Cursor {
|
||||||
val wrapper = BufferCursor(buffer, offset = absolutePosition, size)
|
val wrapper = BufferCursor(buffer, offset = absolutePosition, size)
|
||||||
position += size
|
position += size
|
||||||
return wrapper
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buffer(size: UInt): Buffer {
|
override fun buffer(size: Int): Buffer {
|
||||||
val wrapper = buffer.slice(offset = absolutePosition, size)
|
val wrapper = buffer.slice(offset = absolutePosition, size)
|
||||||
position += size
|
position += size
|
||||||
return wrapper
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeU8(value: UByte): WritableCursor {
|
override fun writeU8(value: UByte): WritableCursor {
|
||||||
ensureSpace(1u)
|
ensureSpace(1)
|
||||||
buffer.setU8(absolutePosition, value)
|
buffer.setU8(absolutePosition, value)
|
||||||
position++
|
position++
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeU16(value: UShort): WritableCursor {
|
override fun writeU16(value: UShort): WritableCursor {
|
||||||
ensureSpace(2u)
|
ensureSpace(2)
|
||||||
buffer.setU16(absolutePosition, value)
|
buffer.setU16(absolutePosition, value)
|
||||||
position += 2u
|
position += 2
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeU32(value: UInt): WritableCursor {
|
override fun writeU32(value: UInt): WritableCursor {
|
||||||
ensureSpace(4u)
|
ensureSpace(4)
|
||||||
buffer.setU32(absolutePosition, value)
|
buffer.setU32(absolutePosition, value)
|
||||||
position += 4u
|
position += 4
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeI8(value: Byte): WritableCursor {
|
override fun writeI8(value: Byte): WritableCursor {
|
||||||
ensureSpace(1u)
|
ensureSpace(1)
|
||||||
buffer.setI8(absolutePosition, value)
|
buffer.setI8(absolutePosition, value)
|
||||||
position++
|
position++
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeI16(value: Short): WritableCursor {
|
override fun writeI16(value: Short): WritableCursor {
|
||||||
ensureSpace(2u)
|
ensureSpace(2)
|
||||||
buffer.setI16(absolutePosition, value)
|
buffer.setI16(absolutePosition, value)
|
||||||
position += 2u
|
position += 2
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeI32(value: Int): WritableCursor {
|
override fun writeI32(value: Int): WritableCursor {
|
||||||
ensureSpace(4u)
|
ensureSpace(4)
|
||||||
buffer.setI32(absolutePosition, value)
|
buffer.setI32(absolutePosition, value)
|
||||||
position += 4u
|
position += 4
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeF32(value: Float): WritableCursor {
|
override fun writeF32(value: Float): WritableCursor {
|
||||||
ensureSpace(4u)
|
ensureSpace(4)
|
||||||
buffer.setF32(absolutePosition, value)
|
buffer.setF32(absolutePosition, value)
|
||||||
position += 4u
|
position += 4
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeU8Array(array: UByteArray): WritableCursor {
|
override fun writeU8Array(array: UByteArray): WritableCursor {
|
||||||
ensureSpace(array.size.toUInt())
|
ensureSpace(array.size)
|
||||||
return super.writeU8Array(array)
|
return super.writeU8Array(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeU16Array(array: UShortArray): WritableCursor {
|
override fun writeU16Array(array: UShortArray): WritableCursor {
|
||||||
ensureSpace(2u * array.size.toUInt())
|
ensureSpace(2 * array.size)
|
||||||
return super.writeU16Array(array)
|
return super.writeU16Array(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeU32Array(array: UIntArray): WritableCursor {
|
override fun writeU32Array(array: UIntArray): WritableCursor {
|
||||||
ensureSpace(4u * array.size.toUInt())
|
ensureSpace(4 * array.size)
|
||||||
return super.writeU32Array(array)
|
return super.writeU32Array(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeI32Array(array: IntArray): WritableCursor {
|
override fun writeI32Array(array: IntArray): WritableCursor {
|
||||||
ensureSpace(4u * array.size.toUInt())
|
ensureSpace(4 * array.size)
|
||||||
return super.writeI32Array(array)
|
return super.writeI32Array(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,21 +225,21 @@ class BufferCursor(
|
|||||||
return super.writeCursor(other)
|
return super.writeCursor(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeStringAscii(str: String, byteLength: UInt): WritableCursor {
|
override fun writeStringAscii(str: String, byteLength: Int): WritableCursor {
|
||||||
ensureSpace(byteLength)
|
ensureSpace(byteLength)
|
||||||
return super.writeStringAscii(str, byteLength)
|
return super.writeStringAscii(str, byteLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeStringUtf16(str: String, byteLength: UInt): WritableCursor {
|
override fun writeStringUtf16(str: String, byteLength: Int): WritableCursor {
|
||||||
ensureSpace(byteLength)
|
ensureSpace(byteLength)
|
||||||
return super.writeStringUtf16(str, byteLength)
|
return super.writeStringUtf16(str, byteLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureSpace(size: UInt) {
|
private fun ensureSpace(size: Int) {
|
||||||
val needed = (position + size).toInt() - _size.toInt()
|
val needed = (position + size) - _size
|
||||||
|
|
||||||
if (needed > 0) {
|
if (needed > 0) {
|
||||||
_size += needed.toUInt()
|
_size += needed
|
||||||
|
|
||||||
if (buffer.size < offset + _size) {
|
if (buffer.size < offset + _size) {
|
||||||
buffer.size = offset + _size
|
buffer.size = offset + _size
|
||||||
@ -247,3 +247,6 @@ class BufferCursor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Buffer.cursor(): BufferCursor =
|
||||||
|
BufferCursor(this)
|
||||||
|
@ -7,21 +7,21 @@ import world.phantasmal.lib.buffer.Buffer
|
|||||||
* A cursor for reading binary data.
|
* A cursor for reading binary data.
|
||||||
*/
|
*/
|
||||||
interface Cursor {
|
interface Cursor {
|
||||||
val size: UInt
|
val size: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position from where bytes will be read or written.
|
* The position from where bytes will be read or written.
|
||||||
*/
|
*/
|
||||||
val position: UInt
|
val position: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Byte order mode.
|
* Byte order mode.
|
||||||
*/
|
*/
|
||||||
var endianness: Endianness
|
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.
|
* 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.
|
* 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.
|
* 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.
|
* 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].
|
* 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].
|
* 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].
|
* 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].
|
* 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.
|
* Consumes a variable number of bytes.
|
||||||
*
|
*
|
||||||
* @param size the amount bytes to consume.
|
* @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.
|
* Consumes up to [maxByteLength] bytes.
|
||||||
*/
|
*/
|
||||||
fun stringAscii(
|
fun stringAscii(
|
||||||
maxByteLength: UInt,
|
maxByteLength: Int,
|
||||||
nullTerminated: Boolean,
|
nullTerminated: Boolean,
|
||||||
dropRemaining: Boolean,
|
dropRemaining: Boolean,
|
||||||
): String
|
): String
|
||||||
@ -121,7 +121,7 @@ interface Cursor {
|
|||||||
* Consumes up to [maxByteLength] bytes.
|
* Consumes up to [maxByteLength] bytes.
|
||||||
*/
|
*/
|
||||||
fun stringUtf16(
|
fun stringUtf16(
|
||||||
maxByteLength: UInt,
|
maxByteLength: Int,
|
||||||
nullTerminated: Boolean,
|
nullTerminated: Boolean,
|
||||||
dropRemaining: Boolean,
|
dropRemaining: Boolean,
|
||||||
): String
|
): String
|
||||||
@ -129,5 +129,5 @@ interface Cursor {
|
|||||||
/**
|
/**
|
||||||
* Returns a buffer with a copy of [size] bytes at [position].
|
* Returns a buffer with a copy of [size] bytes at [position].
|
||||||
*/
|
*/
|
||||||
fun buffer(size: UInt): Buffer
|
fun buffer(size: Int): Buffer
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,13 @@ package world.phantasmal.lib.cursor
|
|||||||
* A cursor for reading and writing binary data.
|
* A cursor for reading and writing binary data.
|
||||||
*/
|
*/
|
||||||
interface WritableCursor : Cursor {
|
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.
|
* 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
|
* Writes [byteLength] characters of [str]. If [str] is shorter than [byteLength], nul bytes
|
||||||
* will be inserted until [byteLength] bytes have been written.
|
* 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
|
* 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]
|
* [byteLength] bytes can be written this way, nul bytes will be inserted until [byteLength]
|
||||||
* bytes have been written.
|
* bytes have been written.
|
||||||
*/
|
*/
|
||||||
fun writeStringUtf16(str: String, byteLength: UInt): WritableCursor
|
fun writeStringUtf16(str: String, byteLength: Int): WritableCursor
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ import world.phantasmal.lib.cursor.Cursor
|
|||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
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.
|
* PSO uses a little endian variant of the IFF format.
|
||||||
@ -27,16 +27,16 @@ fun parseIffHeaders(cursor: Cursor): PwResult<List<IffChunkHeader>> =
|
|||||||
|
|
||||||
private fun <T> parse(
|
private fun <T> parse(
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
getChunk: (Cursor, type: UInt, size: UInt) -> T,
|
getChunk: (Cursor, type: Int, size: Int) -> T,
|
||||||
): PwResult<List<T>> {
|
): PwResult<List<T>> {
|
||||||
val result = PwResult.build<List<T>>(logger)
|
val result = PwResult.build<List<T>>(logger)
|
||||||
val chunks = mutableListOf<T>()
|
val chunks = mutableListOf<T>()
|
||||||
var corrupted = false
|
var corrupted = false
|
||||||
|
|
||||||
while (cursor.bytesLeft >= 8u) {
|
while (cursor.bytesLeft >= 8) {
|
||||||
val type = cursor.u32()
|
val type = cursor.i32()
|
||||||
val sizePos = cursor.position
|
val sizePos = cursor.position
|
||||||
val size = cursor.u32()
|
val size = cursor.i32()
|
||||||
|
|
||||||
if (size > cursor.bytesLeft) {
|
if (size > cursor.bytesLeft) {
|
||||||
corrupted = true
|
corrupted = true
|
||||||
|
@ -8,7 +8,7 @@ import world.phantasmal.lib.fileFormats.Vec3
|
|||||||
import world.phantasmal.lib.fileFormats.parseIff
|
import world.phantasmal.lib.fileFormats.parseIff
|
||||||
import world.phantasmal.lib.fileFormats.vec3F32
|
import world.phantasmal.lib.fileFormats.vec3F32
|
||||||
|
|
||||||
private const val NJCM: UInt = 0x4D434A4Eu
|
private const val NJCM: Int = 0x4D434A4E
|
||||||
|
|
||||||
class NjObject<Model>(
|
class NjObject<Model>(
|
||||||
val evaluationFlags: NjEvaluationFlags,
|
val evaluationFlags: NjEvaluationFlags,
|
||||||
@ -72,7 +72,7 @@ private fun <Model, Context> parseSiblingObjects(
|
|||||||
val skip = (evalFlags and 0b1000000u) != 0u
|
val skip = (evalFlags and 0b1000000u) != 0u
|
||||||
val shapeSkip = (evalFlags and 0b10000000u) != 0u
|
val shapeSkip = (evalFlags and 0b10000000u) != 0u
|
||||||
|
|
||||||
val modelOffset = cursor.u32()
|
val modelOffset = cursor.i32()
|
||||||
val pos = cursor.vec3F32()
|
val pos = cursor.vec3F32()
|
||||||
val rotation = Vec3(
|
val rotation = Vec3(
|
||||||
angleToRad(cursor.i32()),
|
angleToRad(cursor.i32()),
|
||||||
@ -80,24 +80,24 @@ private fun <Model, Context> parseSiblingObjects(
|
|||||||
angleToRad(cursor.i32()),
|
angleToRad(cursor.i32()),
|
||||||
)
|
)
|
||||||
val scale = cursor.vec3F32()
|
val scale = cursor.vec3F32()
|
||||||
val childOffset = cursor.u32()
|
val childOffset = cursor.i32()
|
||||||
val siblingOffset = cursor.u32()
|
val siblingOffset = cursor.i32()
|
||||||
|
|
||||||
val model = if (modelOffset == 0u) {
|
val model = if (modelOffset == 0) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
cursor.seekStart(modelOffset)
|
cursor.seekStart(modelOffset)
|
||||||
parse_model(cursor, context)
|
parse_model(cursor, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
val children = if (childOffset == 0u) {
|
val children = if (childOffset == 0) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
cursor.seekStart(childOffset)
|
cursor.seekStart(childOffset)
|
||||||
parseSiblingObjects(cursor, parse_model, context)
|
parseSiblingObjects(cursor, parse_model, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
val siblings = if (siblingOffset == 0u) {
|
val siblings = if (siblingOffset == 0) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
cursor.seekStart(siblingOffset)
|
cursor.seekStart(siblingOffset)
|
||||||
|
@ -62,7 +62,7 @@ sealed class NjcmChunk(val typeId: UByte) {
|
|||||||
|
|
||||||
class Bits(typeId: UByte, val srcAlpha: UByte, val dstAlpha: UByte) : NjcmChunk(typeId)
|
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)
|
class DrawPolygonList(val cacheIndex: UByte) : NjcmChunk(5u)
|
||||||
|
|
||||||
@ -122,15 +122,15 @@ class NjcmErgb(
|
|||||||
val b: UByte,
|
val b: UByte,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, UInt>): NjcmModel {
|
fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, Int>): NjcmModel {
|
||||||
val vlistOffset = cursor.u32() // Vertex list
|
val vlistOffset = cursor.i32() // Vertex list
|
||||||
val plistOffset = cursor.u32() // Triangle strip index list
|
val plistOffset = cursor.i32() // Triangle strip index list
|
||||||
val boundingSphereCenter = cursor.vec3F32()
|
val boundingSphereCenter = cursor.vec3F32()
|
||||||
val boundingSphereRadius = cursor.f32()
|
val boundingSphereRadius = cursor.f32()
|
||||||
val vertices: MutableList<NjcmVertex> = mutableListOf()
|
val vertices: MutableList<NjcmVertex> = mutableListOf()
|
||||||
val meshes: MutableList<NjcmTriangleStrip> = mutableListOf()
|
val meshes: MutableList<NjcmTriangleStrip> = mutableListOf()
|
||||||
|
|
||||||
if (vlistOffset != 0u) {
|
if (vlistOffset != 0) {
|
||||||
cursor.seekStart(vlistOffset)
|
cursor.seekStart(vlistOffset)
|
||||||
|
|
||||||
for (chunk in parseChunks(cursor, cachedChunkOffsets, true)) {
|
for (chunk in parseChunks(cursor, cachedChunkOffsets, true)) {
|
||||||
@ -148,7 +148,7 @@ fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, UInt>):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plistOffset != 0u) {
|
if (plistOffset != 0) {
|
||||||
cursor.seekStart(plistOffset)
|
cursor.seekStart(plistOffset)
|
||||||
|
|
||||||
var textureId: UInt? = null
|
var textureId: UInt? = null
|
||||||
@ -203,7 +203,7 @@ fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, UInt>):
|
|||||||
// TODO: don't reparse when DrawPolygonList chunk is encountered.
|
// TODO: don't reparse when DrawPolygonList chunk is encountered.
|
||||||
private fun parseChunks(
|
private fun parseChunks(
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
cachedChunkOffsets: MutableMap<UByte, UInt>,
|
cachedChunkOffsets: MutableMap<UByte, Int>,
|
||||||
wideEndChunks: Boolean,
|
wideEndChunks: Boolean,
|
||||||
): List<NjcmChunk> {
|
): List<NjcmChunk> {
|
||||||
val chunks: MutableList<NjcmChunk> = mutableListOf()
|
val chunks: MutableList<NjcmChunk> = mutableListOf()
|
||||||
@ -214,7 +214,7 @@ private fun parseChunks(
|
|||||||
val flags = cursor.u8()
|
val flags = cursor.u8()
|
||||||
val flagsUInt = flags.toUInt()
|
val flagsUInt = flags.toUInt()
|
||||||
val chunkStartPosition = cursor.position
|
val chunkStartPosition = cursor.position
|
||||||
var size = 0u
|
var size = 0
|
||||||
|
|
||||||
when (typeId.toInt()) {
|
when (typeId.toInt()) {
|
||||||
0 -> {
|
0 -> {
|
||||||
@ -253,7 +253,7 @@ private fun parseChunks(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
in 8..9 -> {
|
in 8..9 -> {
|
||||||
size = 2u
|
size = 2
|
||||||
val textureBitsAndId = cursor.u16().toUInt()
|
val textureBitsAndId = cursor.u16().toUInt()
|
||||||
|
|
||||||
chunks.add(NjcmChunk.Tiny(
|
chunks.add(NjcmChunk.Tiny(
|
||||||
@ -269,7 +269,7 @@ private fun parseChunks(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
in 17..31 -> {
|
in 17..31 -> {
|
||||||
size = 2u + 2u * cursor.u16()
|
size = 2 + 2 * cursor.i16()
|
||||||
|
|
||||||
var diffuse: NjcmArgb? = null
|
var diffuse: NjcmArgb? = null
|
||||||
var ambient: NjcmArgb? = null
|
var ambient: NjcmArgb? = null
|
||||||
@ -312,32 +312,32 @@ private fun parseChunks(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
in 32..50 -> {
|
in 32..50 -> {
|
||||||
size = 2u + 4u * cursor.u16()
|
size = 2 + 4 * cursor.i16()
|
||||||
chunks.add(NjcmChunk.Vertex(
|
chunks.add(NjcmChunk.Vertex(
|
||||||
typeId,
|
typeId,
|
||||||
vertices = parseVertexChunk(cursor, typeId, flags),
|
vertices = parseVertexChunk(cursor, typeId, flags),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
in 56..58 -> {
|
in 56..58 -> {
|
||||||
size = 2u + 2u * cursor.u16()
|
size = 2 + 2 * cursor.i16()
|
||||||
chunks.add(NjcmChunk.Volume(
|
chunks.add(NjcmChunk.Volume(
|
||||||
typeId,
|
typeId,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
in 64..75 -> {
|
in 64..75 -> {
|
||||||
size = 2u + 2u * cursor.u16()
|
size = 2 + 2 * cursor.i16()
|
||||||
chunks.add(NjcmChunk.Strip(
|
chunks.add(NjcmChunk.Strip(
|
||||||
typeId,
|
typeId,
|
||||||
triangleStrips = parseTriangleStripChunk(cursor, typeId, flags),
|
triangleStrips = parseTriangleStripChunk(cursor, typeId, flags),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
255 -> {
|
255 -> {
|
||||||
size = if (wideEndChunks) 2u else 0u
|
size = if (wideEndChunks) 2 else 0
|
||||||
chunks.add(NjcmChunk.End)
|
chunks.add(NjcmChunk.End)
|
||||||
loop = false
|
loop = false
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
size = 2u + 2u * cursor.u16()
|
size = 2 + 2 * cursor.i16()
|
||||||
chunks.add(NjcmChunk.Unknown(
|
chunks.add(NjcmChunk.Unknown(
|
||||||
typeId,
|
typeId,
|
||||||
))
|
))
|
||||||
|
@ -6,9 +6,9 @@ import world.phantasmal.lib.cursor.Cursor
|
|||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
private const val DC_GC_OBJECT_CODE_OFFSET = 468u
|
private const val DC_GC_OBJECT_CODE_OFFSET = 468
|
||||||
private const val PC_OBJECT_CODE_OFFSET = 920u
|
private const val PC_OBJECT_CODE_OFFSET = 920
|
||||||
private const val BB_OBJECT_CODE_OFFSET = 4652u
|
private const val BB_OBJECT_CODE_OFFSET = 4652
|
||||||
|
|
||||||
class BinFile(
|
class BinFile(
|
||||||
val format: BinFormat,
|
val format: BinFormat,
|
||||||
@ -40,17 +40,19 @@ enum class BinFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun parseBin(cursor: Cursor): BinFile {
|
fun parseBin(cursor: Cursor): BinFile {
|
||||||
val objectCodeOffset = cursor.u32()
|
val objectCodeOffset = cursor.i32()
|
||||||
val labelOffsetTableOffset = cursor.u32() // Relative offsets
|
val labelOffsetTableOffset = cursor.i32() // Relative offsets
|
||||||
val size = cursor.u32()
|
val size = cursor.i32()
|
||||||
cursor.seek(4) // Always seems to be 0xFFFFFFFF.
|
cursor.seek(4) // Always seems to be 0xFFFFFFFF.
|
||||||
|
|
||||||
val format = when (objectCodeOffset) {
|
val format = when (objectCodeOffset) {
|
||||||
DC_GC_OBJECT_CODE_OFFSET -> BinFormat.DC_GC
|
DC_GC_OBJECT_CODE_OFFSET -> BinFormat.DC_GC
|
||||||
BB_OBJECT_CODE_OFFSET -> BinFormat.BB
|
|
||||||
PC_OBJECT_CODE_OFFSET -> BinFormat.PC
|
PC_OBJECT_CODE_OFFSET -> BinFormat.PC
|
||||||
|
BB_OBJECT_CODE_OFFSET -> BinFormat.BB
|
||||||
else -> {
|
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
|
BinFormat.PC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,15 +67,15 @@ fun parseBin(cursor: Cursor): BinFile {
|
|||||||
cursor.seek(1)
|
cursor.seek(1)
|
||||||
language = cursor.u8().toUInt()
|
language = cursor.u8().toUInt()
|
||||||
questId = cursor.u16().toUInt()
|
questId = cursor.u16().toUInt()
|
||||||
questName = cursor.stringAscii(32u, nullTerminated = true, dropRemaining = true)
|
questName = cursor.stringAscii(32, nullTerminated = true, dropRemaining = true)
|
||||||
shortDescription = cursor.stringAscii(128u, nullTerminated = true, dropRemaining = true)
|
shortDescription = cursor.stringAscii(128, nullTerminated = true, dropRemaining = true)
|
||||||
longDescription = cursor.stringAscii(288u, nullTerminated = true, dropRemaining = true)
|
longDescription = cursor.stringAscii(288, nullTerminated = true, dropRemaining = true)
|
||||||
} else {
|
} else {
|
||||||
questId = cursor.u32()
|
questId = cursor.u32()
|
||||||
language = cursor.u32()
|
language = cursor.u32()
|
||||||
questName = cursor.stringUtf16(64u, nullTerminated = true, dropRemaining = true)
|
questName = cursor.stringUtf16(64, nullTerminated = true, dropRemaining = true)
|
||||||
shortDescription = cursor.stringUtf16(256u, nullTerminated = true, dropRemaining = true)
|
shortDescription = cursor.stringUtf16(256, nullTerminated = true, dropRemaining = true)
|
||||||
longDescription = cursor.stringUtf16(576u, nullTerminated = true, dropRemaining = true)
|
longDescription = cursor.stringUtf16(576, nullTerminated = true, dropRemaining = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size != cursor.size) {
|
if (size != cursor.size) {
|
||||||
@ -82,12 +84,12 @@ fun parseBin(cursor: Cursor): BinFile {
|
|||||||
|
|
||||||
val shopItems = if (format == BinFormat.BB) {
|
val shopItems = if (format == BinFormat.BB) {
|
||||||
cursor.seek(4) // Skip padding.
|
cursor.seek(4) // Skip padding.
|
||||||
cursor.u32Array(932u)
|
cursor.u32Array(932)
|
||||||
} else {
|
} else {
|
||||||
UIntArray(0)
|
UIntArray(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val labelOffsetCount = (cursor.size - labelOffsetTableOffset) / 4u
|
val labelOffsetCount = (cursor.size - labelOffsetTableOffset) / 4
|
||||||
val labelOffsets = cursor
|
val labelOffsets = cursor
|
||||||
.seekStart(labelOffsetTableOffset)
|
.seekStart(labelOffsetTableOffset)
|
||||||
.i32Array(labelOffsetCount)
|
.i32Array(labelOffsetCount)
|
||||||
|
@ -6,13 +6,13 @@ import world.phantasmal.lib.cursor.Cursor
|
|||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
private const val EVENT_ACTION_SPAWN_NPCS: UByte = 0x8u
|
private const val EVENT_ACTION_SPAWN_NPCS = 0x8
|
||||||
private const val EVENT_ACTION_UNLOCK: UByte = 0xAu
|
private const val EVENT_ACTION_UNLOCK = 0xA
|
||||||
private const val EVENT_ACTION_LOCK: UByte = 0xBu
|
private const val EVENT_ACTION_LOCK = 0xB
|
||||||
private const val EVENT_ACTION_TRIGGER_EVENT: UByte = 0xCu
|
private const val EVENT_ACTION_TRIGGER_EVENT = 0xC
|
||||||
|
|
||||||
const val OBJECT_BYTE_SIZE = 68u
|
const val OBJECT_BYTE_SIZE = 68
|
||||||
const val NPC_BYTE_SIZE = 72u
|
const val NPC_BYTE_SIZE = 72
|
||||||
|
|
||||||
class DatFile(
|
class DatFile(
|
||||||
val objs: List<DatEntity>,
|
val objs: List<DatEntity>,
|
||||||
@ -22,7 +22,7 @@ class DatFile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class DatEntity(
|
class DatEntity(
|
||||||
var areaId: UInt,
|
var areaId: Int,
|
||||||
val data: Buffer,
|
val data: Buffer,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class DatEvent(
|
|||||||
var wave: UShort,
|
var wave: UShort,
|
||||||
var delay: UShort,
|
var delay: UShort,
|
||||||
val actions: MutableList<DatEventAction>,
|
val actions: MutableList<DatEventAction>,
|
||||||
val areaId: UInt,
|
val areaId: Int,
|
||||||
val unknown: UShort,
|
val unknown: UShort,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,10 +56,10 @@ sealed class DatEventAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DatUnknown(
|
class DatUnknown(
|
||||||
val entityType: UInt,
|
val entityType: Int,
|
||||||
val totalSize: UInt,
|
val totalSize: Int,
|
||||||
val areaId: UInt,
|
val areaId: Int,
|
||||||
val entitiesSize: UInt,
|
val entitiesSize: Int,
|
||||||
val data: UByteArray,
|
val data: UByteArray,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,24 +70,24 @@ fun parseDat(cursor: Cursor): DatFile {
|
|||||||
val unknowns = mutableListOf<DatUnknown>()
|
val unknowns = mutableListOf<DatUnknown>()
|
||||||
|
|
||||||
while (cursor.hasBytesLeft()) {
|
while (cursor.hasBytesLeft()) {
|
||||||
val entityType = cursor.u32()
|
val entityType = cursor.i32()
|
||||||
val totalSize = cursor.u32()
|
val totalSize = cursor.i32()
|
||||||
val areaId = cursor.u32()
|
val areaId = cursor.i32()
|
||||||
val entitiesSize = cursor.u32()
|
val entitiesSize = cursor.i32()
|
||||||
|
|
||||||
if (entityType == 0u) {
|
if (entityType == 0) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
require(entitiesSize == totalSize - 16u) {
|
require(entitiesSize == totalSize - 16) {
|
||||||
"Malformed DAT file. Expected an entities size of ${totalSize - 16u}, got ${entitiesSize}."
|
"Malformed DAT file. Expected an entities size of ${totalSize - 16}, got ${entitiesSize}."
|
||||||
}
|
}
|
||||||
|
|
||||||
val entitiesCursor = cursor.take(entitiesSize)
|
val entitiesCursor = cursor.take(entitiesSize)
|
||||||
|
|
||||||
when (entityType) {
|
when (entityType) {
|
||||||
1u -> parseEntities(entitiesCursor, areaId, objs, OBJECT_BYTE_SIZE)
|
1 -> parseEntities(entitiesCursor, areaId, objs, OBJECT_BYTE_SIZE)
|
||||||
2u -> parseEntities(entitiesCursor, areaId, npcs, NPC_BYTE_SIZE)
|
2 -> parseEntities(entitiesCursor, areaId, npcs, NPC_BYTE_SIZE)
|
||||||
3u -> parseEvents(entitiesCursor, areaId, events)
|
3 -> parseEvents(entitiesCursor, areaId, events)
|
||||||
else -> {
|
else -> {
|
||||||
// Unknown entity types 4 and 5 (challenge mode).
|
// Unknown entity types 4 and 5 (challenge mode).
|
||||||
unknowns.add(DatUnknown(
|
unknowns.add(DatUnknown(
|
||||||
@ -118,13 +118,13 @@ fun parseDat(cursor: Cursor): DatFile {
|
|||||||
|
|
||||||
private fun parseEntities(
|
private fun parseEntities(
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
areaId: UInt,
|
areaId: Int,
|
||||||
entities: MutableList<DatEntity>,
|
entities: MutableList<DatEntity>,
|
||||||
entitySize: UInt,
|
entitySize: Int,
|
||||||
) {
|
) {
|
||||||
val entityCount = cursor.size / entitySize
|
val entityCount = cursor.size / entitySize
|
||||||
|
|
||||||
repeat(entityCount.toInt()) {
|
repeat(entityCount) {
|
||||||
entities.add(DatEntity(
|
entities.add(DatEntity(
|
||||||
areaId,
|
areaId,
|
||||||
data = cursor.buffer(entitySize),
|
data = cursor.buffer(entitySize),
|
||||||
@ -132,29 +132,29 @@ private fun parseEntities(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseEvents(cursor: Cursor, areaId: UInt, events: MutableList<DatEvent>) {
|
private fun parseEvents(cursor: Cursor, areaId: Int, events: MutableList<DatEvent>) {
|
||||||
val actionsOffset = cursor.u32()
|
val actionsOffset = cursor.i32()
|
||||||
cursor.seek(4) // Always 0x10
|
cursor.seek(4) // Always 0x10
|
||||||
val eventCount = cursor.u32()
|
val eventCount = cursor.i32()
|
||||||
cursor.seek(3) // Always 0
|
cursor.seek(3) // Always 0
|
||||||
val eventType = cursor.u8()
|
val eventType = cursor.u8()
|
||||||
|
|
||||||
require(eventType == (0x32u).toUByte()) {
|
require(eventType != (0x32u).toUByte()) {
|
||||||
"Can't parse challenge mode quests yet."
|
"Can't parse challenge mode quests yet."
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.seekStart(actionsOffset)
|
cursor.seekStart(actionsOffset)
|
||||||
val actionsCursor = cursor.take(cursor.bytesLeft)
|
val actionsCursor = cursor.take(cursor.bytesLeft)
|
||||||
cursor.seekStart(16u)
|
cursor.seekStart(16)
|
||||||
|
|
||||||
repeat(eventCount.toInt()) {
|
repeat(eventCount) {
|
||||||
val id = cursor.u32()
|
val id = cursor.u32()
|
||||||
cursor.seek(4) // Always 0x100
|
cursor.seek(4) // Always 0x100
|
||||||
val sectionId = cursor.u16()
|
val sectionId = cursor.u16()
|
||||||
val wave = cursor.u16()
|
val wave = cursor.u16()
|
||||||
val delay = cursor.u16()
|
val delay = cursor.u16()
|
||||||
val unknown = cursor.u16() // "wavesetting"?
|
val unknown = cursor.u16() // "wavesetting"?
|
||||||
val eventActionsOffset = cursor.u32()
|
val eventActionsOffset = cursor.i32()
|
||||||
|
|
||||||
val actions: MutableList<DatEventAction> =
|
val actions: MutableList<DatEventAction> =
|
||||||
if (eventActionsOffset < actionsCursor.size) {
|
if (eventActionsOffset < actionsCursor.size) {
|
||||||
@ -178,7 +178,7 @@ private fun parseEvents(cursor: Cursor, areaId: UInt, events: MutableList<DatEve
|
|||||||
|
|
||||||
if (cursor.position != actionsOffset) {
|
if (cursor.position != actionsOffset) {
|
||||||
logger.warn {
|
logger.warn {
|
||||||
"Read ${cursor.position - 16u} bytes of event data instead of expected ${actionsOffset - 16u}."
|
"Read ${cursor.position - 16} bytes of event data instead of expected ${actionsOffset - 16}."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,8 +204,8 @@ private fun parseEventActions(cursor: Cursor): MutableList<DatEventAction> {
|
|||||||
val actions = mutableListOf<DatEventAction>()
|
val actions = mutableListOf<DatEventAction>()
|
||||||
|
|
||||||
outer@ while (cursor.hasBytesLeft()) {
|
outer@ while (cursor.hasBytesLeft()) {
|
||||||
when (val type = cursor.u8()) {
|
when (val type = cursor.u8().toInt()) {
|
||||||
(1u).toUByte() -> break@outer
|
1 -> break@outer
|
||||||
|
|
||||||
EVENT_ACTION_SPAWN_NPCS ->
|
EVENT_ACTION_SPAWN_NPCS ->
|
||||||
actions.add(DatEventAction.SpawnNpcs(
|
actions.add(DatEventAction.SpawnNpcs(
|
||||||
|
@ -67,7 +67,7 @@ fun parseObjectCode(
|
|||||||
// Put segments in an array and parse left-over segments as data.
|
// Put segments in an array and parse left-over segments as data.
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
while (offset < cursor.size.toInt()) {
|
while (offset < cursor.size) {
|
||||||
var segment: Segment? = offsetToSegment[offset]
|
var segment: Segment? = offsetToSegment[offset]
|
||||||
|
|
||||||
// If we have a segment, add it. Otherwise create a new data segment.
|
// If we have a segment, add it. Otherwise create a new data segment.
|
||||||
@ -76,7 +76,7 @@ fun parseObjectCode(
|
|||||||
var endOffset: Int
|
var endOffset: Int
|
||||||
|
|
||||||
if (labels == null) {
|
if (labels == null) {
|
||||||
endOffset = cursor.size.toInt()
|
endOffset = cursor.size
|
||||||
|
|
||||||
for (label in labelHolder.labels) {
|
for (label in labelHolder.labels) {
|
||||||
if (label.offset > offset) {
|
if (label.offset > offset) {
|
||||||
@ -86,10 +86,10 @@ fun parseObjectCode(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val info = labelHolder.getInfo(labels[0])!!
|
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(
|
parseDataSegment(
|
||||||
offsetToSegment,
|
offsetToSegment,
|
||||||
cursor,
|
cursor,
|
||||||
@ -110,7 +110,7 @@ fun parseObjectCode(
|
|||||||
offset += when (segment) {
|
offset += when (segment) {
|
||||||
is InstructionSegment -> segment.instructions.sumBy { instructionSize(it, dcGcFormat) }
|
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.
|
// String segments should be multiples of 4 bytes.
|
||||||
is StringSegment -> 4 * ceil((segment.value.length + 1) / 2.0).toInt()
|
is StringSegment -> 4 * ceil((segment.value.length + 1) / 2.0).toInt()
|
||||||
@ -136,7 +136,7 @@ fun parseObjectCode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check parsed object code.
|
// Sanity check parsed object code.
|
||||||
if (cursor.size != offset.toUInt()) {
|
if (cursor.size != offset) {
|
||||||
result.addProblem(
|
result.addProblem(
|
||||||
Severity.Error,
|
Severity.Error,
|
||||||
"The script code is corrupt.",
|
"The script code is corrupt.",
|
||||||
@ -201,7 +201,8 @@ private fun findAndParseSegments(
|
|||||||
// Never on the stack.
|
// Never on the stack.
|
||||||
// Eat all remaining arguments.
|
// Eat all remaining arguments.
|
||||||
while (i < instruction.args.size) {
|
while (i < instruction.args.size) {
|
||||||
newLabels[instruction.args[i].value as Int] = SegmentType.Instructions
|
newLabels[instruction.args[i].value as Int] =
|
||||||
|
SegmentType.Instructions
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,8 +323,8 @@ private fun parseSegment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val endOffset = info.next?.offset ?: cursor.size.toInt()
|
val endOffset = info.next?.offset ?: cursor.size
|
||||||
cursor.seekStart(info.offset.toUInt())
|
cursor.seekStart(info.offset)
|
||||||
|
|
||||||
return when (type) {
|
return when (type) {
|
||||||
SegmentType.Instructions ->
|
SegmentType.Instructions ->
|
||||||
@ -370,9 +371,9 @@ private fun parseInstructionsSegment(
|
|||||||
instructions,
|
instructions,
|
||||||
SegmentSrcLoc()
|
SegmentSrcLoc()
|
||||||
)
|
)
|
||||||
offsetToSegment[cursor.position.toInt()] = segment
|
offsetToSegment[cursor.position] = segment
|
||||||
|
|
||||||
while (cursor.position < endOffset.toUInt()) {
|
while (cursor.position < endOffset) {
|
||||||
// Parse the opcode.
|
// Parse the opcode.
|
||||||
val mainOpcode = cursor.u8()
|
val mainOpcode = cursor.u8()
|
||||||
|
|
||||||
@ -436,10 +437,10 @@ private fun parseDataSegment(
|
|||||||
val startOffset = cursor.position
|
val startOffset = cursor.position
|
||||||
val segment = DataSegment(
|
val segment = DataSegment(
|
||||||
labels,
|
labels,
|
||||||
cursor.buffer(endOffset.toUInt() - startOffset),
|
cursor.buffer(endOffset - startOffset),
|
||||||
SegmentSrcLoc(),
|
SegmentSrcLoc(),
|
||||||
)
|
)
|
||||||
offsetToSegment[startOffset.toInt()] = segment
|
offsetToSegment[startOffset] = segment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStringSegment(
|
private fun parseStringSegment(
|
||||||
@ -454,20 +455,20 @@ private fun parseStringSegment(
|
|||||||
labels,
|
labels,
|
||||||
if (dcGcFormat) {
|
if (dcGcFormat) {
|
||||||
cursor.stringAscii(
|
cursor.stringAscii(
|
||||||
endOffset.toUInt() - startOffset,
|
endOffset - startOffset,
|
||||||
nullTerminated = true,
|
nullTerminated = true,
|
||||||
dropRemaining = true
|
dropRemaining = true
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
cursor.stringUtf16(
|
cursor.stringUtf16(
|
||||||
endOffset.toUInt() - startOffset,
|
endOffset - startOffset,
|
||||||
nullTerminated = true,
|
nullTerminated = true,
|
||||||
dropRemaining = true
|
dropRemaining = true
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
SegmentSrcLoc()
|
SegmentSrcLoc()
|
||||||
)
|
)
|
||||||
offsetToSegment[startOffset.toInt()] = segment
|
offsetToSegment[startOffset] = segment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseInstructionArguments(
|
private fun parseInstructionArguments(
|
||||||
@ -501,7 +502,7 @@ private fun parseInstructionArguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is StringType -> {
|
is StringType -> {
|
||||||
val maxBytes = min(4096u, cursor.bytesLeft)
|
val maxBytes = min(4096, cursor.bytesLeft)
|
||||||
args.add(Arg(
|
args.add(Arg(
|
||||||
if (dcGcFormat) {
|
if (dcGcFormat) {
|
||||||
cursor.stringAscii(
|
cursor.stringAscii(
|
||||||
@ -521,7 +522,7 @@ private fun parseInstructionArguments(
|
|||||||
|
|
||||||
is ILabelVarType -> {
|
is ILabelVarType -> {
|
||||||
val argSize = cursor.u8()
|
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,
|
is RegRefType,
|
||||||
@ -532,7 +533,7 @@ private fun parseInstructionArguments(
|
|||||||
|
|
||||||
is RegRefVarType -> {
|
is RegRefVarType -> {
|
||||||
val argSize = cursor.u8()
|
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.")
|
else -> error("Parameter type ${param.type} not implemented.")
|
||||||
|
@ -8,15 +8,15 @@ class QuestNpc(var episode: Episode, var areaId: Int, val data: Buffer) {
|
|||||||
* Only seems to be valid for non-enemies.
|
* Only seems to be valid for non-enemies.
|
||||||
*/
|
*/
|
||||||
var scriptLabel: Int
|
var scriptLabel: Int
|
||||||
get() = data.getF32(60u).roundToInt()
|
get() = data.getF32(60).roundToInt()
|
||||||
set(value) {
|
set(value) {
|
||||||
data.setF32(60u, value.toFloat())
|
data.setF32(60, value.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
var skin: Int
|
var skin: Int
|
||||||
get() = data.getI32(64u)
|
get() = data.getI32(64)
|
||||||
set(value) {
|
set(value) {
|
||||||
data.setI32(64u, value)
|
data.setI32(64, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -3,9 +3,11 @@ package world.phantasmal.lib.fileFormats.quest
|
|||||||
import world.phantasmal.lib.buffer.Buffer
|
import world.phantasmal.lib.buffer.Buffer
|
||||||
|
|
||||||
class QuestObject(var areaId: Int, val data: Buffer) {
|
class QuestObject(var areaId: Int, val data: Buffer) {
|
||||||
var type: ObjectType = TODO()
|
var type: ObjectType
|
||||||
val scriptLabel: Int? = TODO()
|
get() = TODO()
|
||||||
val scriptLabel2: Int? = TODO()
|
set(_) = TODO()
|
||||||
|
val scriptLabel: Int? = null // TODO Implement scriptLabel.
|
||||||
|
val scriptLabel2: Int? = null // TODO Implement scriptLabel2.
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(data.size == OBJECT_BYTE_SIZE) {
|
require(data.size == OBJECT_BYTE_SIZE) {
|
||||||
|
@ -7,29 +7,56 @@ import kotlin.test.assertTrue
|
|||||||
|
|
||||||
class BufferTests {
|
class BufferTests {
|
||||||
@Test
|
@Test
|
||||||
fun simple_properties_and_invariants() {
|
fun withCapacity() {
|
||||||
val capacity = 500u
|
val capacity = 500
|
||||||
val buffer = Buffer.withCapacity(capacity)
|
val buffer = Buffer.withCapacity(capacity)
|
||||||
|
|
||||||
assertEquals(0u, buffer.size)
|
assertEquals(0, buffer.size)
|
||||||
assertEquals(capacity, buffer.capacity)
|
assertEquals(capacity, buffer.capacity)
|
||||||
assertEquals(Endianness.Little, buffer.endianness)
|
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
|
@Test
|
||||||
fun reallocates_internal_storage_when_necessary() {
|
fun reallocates_internal_storage_when_necessary() {
|
||||||
val buffer = Buffer.withCapacity(100u)
|
val buffer = Buffer.withCapacity(100)
|
||||||
|
|
||||||
assertEquals(0u, buffer.size)
|
assertEquals(0, buffer.size)
|
||||||
assertEquals(100u, buffer.capacity)
|
assertEquals(100, buffer.capacity)
|
||||||
|
|
||||||
buffer.size = 101u
|
buffer.size = 101
|
||||||
|
|
||||||
assertEquals(101u, buffer.size)
|
assertEquals(101, buffer.size)
|
||||||
assertTrue(buffer.capacity >= 101u)
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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}."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,20 +53,20 @@ class BufferCursorTests : WritableCursorTests() {
|
|||||||
val expectedNumber1 = 7891378
|
val expectedNumber1 = 7891378
|
||||||
val expectedNumber2 = 893894273
|
val expectedNumber2 = 893894273
|
||||||
|
|
||||||
val buffer = Buffer.withCapacity(8u, endianness)
|
val buffer = Buffer.withCapacity(8, endianness)
|
||||||
val cursor = BufferCursor(buffer)
|
val cursor = BufferCursor(buffer)
|
||||||
|
|
||||||
assertEquals(0u, buffer.size)
|
assertEquals(0, buffer.size)
|
||||||
assertEquals(0u, cursor.size)
|
assertEquals(0, cursor.size)
|
||||||
|
|
||||||
cursor.write(expectedNumber1)
|
cursor.write(expectedNumber1)
|
||||||
|
|
||||||
assertEquals(byteCount.toUInt(), buffer.size)
|
assertEquals(byteCount, buffer.size)
|
||||||
assertEquals(byteCount.toUInt(), cursor.size)
|
assertEquals(byteCount, cursor.size)
|
||||||
|
|
||||||
cursor.write(expectedNumber2)
|
cursor.write(expectedNumber2)
|
||||||
|
|
||||||
assertEquals(2u * byteCount.toUInt(), buffer.size)
|
assertEquals(2 * byteCount, buffer.size)
|
||||||
assertEquals(2u * byteCount.toUInt(), cursor.size)
|
assertEquals(2 * byteCount, cursor.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,15 +21,15 @@ abstract class CursorTests {
|
|||||||
val cursor = createCursor(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), endianness)
|
val cursor = createCursor(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), endianness)
|
||||||
|
|
||||||
for ((seek_to, expectedPos) in listOf(
|
for ((seek_to, expectedPos) in listOf(
|
||||||
0 to 0u,
|
0 to 0,
|
||||||
3 to 3u,
|
3 to 3,
|
||||||
5 to 8u,
|
5 to 8,
|
||||||
2 to 10u,
|
2 to 10,
|
||||||
-10 to 0u,
|
-10 to 0,
|
||||||
)) {
|
)) {
|
||||||
cursor.seek(seek_to)
|
cursor.seek(seek_to)
|
||||||
|
|
||||||
assertEquals(10u, cursor.size)
|
assertEquals(10, cursor.size)
|
||||||
assertEquals(expectedPos, cursor.position)
|
assertEquals(expectedPos, cursor.position)
|
||||||
assertEquals(cursor.position + cursor.bytesLeft, cursor.size)
|
assertEquals(cursor.position + cursor.bytesLeft, cursor.size)
|
||||||
assertEquals(endianness, cursor.endianness)
|
assertEquals(endianness, cursor.endianness)
|
||||||
@ -116,10 +116,10 @@ abstract class CursorTests {
|
|||||||
val cursor = createCursor(bytes, endianness)
|
val cursor = createCursor(bytes, endianness)
|
||||||
|
|
||||||
assertEquals(expectedNumber1, cursor.read())
|
assertEquals(expectedNumber1, cursor.read())
|
||||||
assertEquals(byteCount.toUInt(), cursor.position)
|
assertEquals(byteCount, cursor.position)
|
||||||
|
|
||||||
assertEquals(expectedNumber2, cursor.read())
|
assertEquals(expectedNumber2, cursor.read())
|
||||||
assertEquals(2u * byteCount.toUInt(), cursor.position)
|
assertEquals(2 * byteCount, cursor.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -139,17 +139,17 @@ abstract class CursorTests {
|
|||||||
val cursor = createCursor(bytes, endianness)
|
val cursor = createCursor(bytes, endianness)
|
||||||
|
|
||||||
assertEquals(2.5f, cursor.f32())
|
assertEquals(2.5f, cursor.f32())
|
||||||
assertEquals(4u, cursor.position)
|
assertEquals(4, cursor.position)
|
||||||
|
|
||||||
assertEquals(32.25f, cursor.f32())
|
assertEquals(32.25f, cursor.f32())
|
||||||
assertEquals(8u, cursor.position)
|
assertEquals(8, cursor.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun u8Array() {
|
fun u8Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = u8Array(n)
|
val arr = u8Array(n)
|
||||||
IntArray(n.toInt()) { arr[it].toInt() }
|
IntArray(n) { arr[it].toInt() }
|
||||||
}
|
}
|
||||||
|
|
||||||
testIntegerArrayRead(1, read, Endianness.Little)
|
testIntegerArrayRead(1, read, Endianness.Little)
|
||||||
@ -158,9 +158,9 @@ abstract class CursorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun u16Array() {
|
fun u16Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = u16Array(n)
|
val arr = u16Array(n)
|
||||||
IntArray(n.toInt()) { arr[it].toInt() }
|
IntArray(n) { arr[it].toInt() }
|
||||||
}
|
}
|
||||||
|
|
||||||
testIntegerArrayRead(2, read, Endianness.Little)
|
testIntegerArrayRead(2, read, Endianness.Little)
|
||||||
@ -169,9 +169,9 @@ abstract class CursorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun u32Array() {
|
fun u32Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = u32Array(n)
|
val arr = u32Array(n)
|
||||||
IntArray(n.toInt()) { arr[it].toInt() }
|
IntArray(n) { arr[it].toInt() }
|
||||||
}
|
}
|
||||||
|
|
||||||
testIntegerArrayRead(4, read, Endianness.Little)
|
testIntegerArrayRead(4, read, Endianness.Little)
|
||||||
@ -180,9 +180,9 @@ abstract class CursorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun i32Array() {
|
fun i32Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = i32Array(n)
|
val arr = i32Array(n)
|
||||||
IntArray(n.toInt()) { arr[it] }
|
IntArray(n) { arr[it] }
|
||||||
}
|
}
|
||||||
|
|
||||||
testIntegerArrayRead(4, read, Endianness.Little)
|
testIntegerArrayRead(4, read, Endianness.Little)
|
||||||
@ -191,7 +191,7 @@ abstract class CursorTests {
|
|||||||
|
|
||||||
private fun testIntegerArrayRead(
|
private fun testIntegerArrayRead(
|
||||||
byteCount: Int,
|
byteCount: Int,
|
||||||
read: Cursor.(UInt) -> IntArray,
|
read: Cursor.(Int) -> IntArray,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
) {
|
) {
|
||||||
// Generate array of the form 1, 2, 0xFF, 4, 5, 6, 7, 8.
|
// Generate array of the form 1, 2, 0xFF, 4, 5, 6, 7, 8.
|
||||||
@ -217,26 +217,47 @@ abstract class CursorTests {
|
|||||||
// Test cursor.
|
// Test cursor.
|
||||||
val cursor = createCursor(bytes, endianness)
|
val cursor = createCursor(bytes, endianness)
|
||||||
|
|
||||||
val array1 = cursor.read(3u)
|
val array1 = cursor.read(3)
|
||||||
assertEquals(1, array1[0])
|
assertEquals(1, array1[0])
|
||||||
assertEquals(2, array1[1])
|
assertEquals(2, array1[1])
|
||||||
assertEquals(allOnes, array1[2])
|
assertEquals(allOnes, array1[2])
|
||||||
assertEquals(3u * byteCount.toUInt(), cursor.position)
|
assertEquals(3 * byteCount, cursor.position)
|
||||||
|
|
||||||
cursor.seekStart((2 * byteCount).toUInt())
|
cursor.seekStart(2 * byteCount)
|
||||||
val array2 = cursor.read(4u)
|
val array2 = cursor.read(4)
|
||||||
assertEquals(allOnes, array2[0])
|
assertEquals(allOnes, array2[0])
|
||||||
assertEquals(4, array2[1])
|
assertEquals(4, array2[1])
|
||||||
assertEquals(5, array2[2])
|
assertEquals(5, array2[2])
|
||||||
assertEquals(6, array2[3])
|
assertEquals(6, array2[3])
|
||||||
assertEquals(6u * byteCount.toUInt(), cursor.position)
|
assertEquals(6 * byteCount, cursor.position)
|
||||||
|
|
||||||
cursor.seekStart((5 * byteCount).toUInt())
|
cursor.seekStart(5 * byteCount)
|
||||||
val array3 = cursor.read(3u)
|
val array3 = cursor.read(3)
|
||||||
assertEquals(6, array3[0])
|
assertEquals(6, array3[0])
|
||||||
assertEquals(7, array3[1])
|
assertEquals(7, array3[1])
|
||||||
assertEquals(8, array3[2])
|
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
|
@Test
|
||||||
@ -254,7 +275,7 @@ abstract class CursorTests {
|
|||||||
private fun testStringRead(
|
private fun testStringRead(
|
||||||
byteCount: Int,
|
byteCount: Int,
|
||||||
read: Cursor.(
|
read: Cursor.(
|
||||||
maxByteLength: UInt,
|
maxByteLength: Int,
|
||||||
nullTerminated: Boolean,
|
nullTerminated: Boolean,
|
||||||
dropRemaining: Boolean,
|
dropRemaining: Boolean,
|
||||||
) -> String,
|
) -> String,
|
||||||
@ -271,30 +292,29 @@ abstract class CursorTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val bc = byteCount.toUInt()
|
|
||||||
val cursor = createCursor(bytes, endianness)
|
val cursor = createCursor(bytes, endianness)
|
||||||
|
|
||||||
cursor.seekStart(bc)
|
cursor.seekStart(byteCount)
|
||||||
assertEquals("AB", cursor.read(4u * bc, true, true))
|
assertEquals("AB", cursor.read(4 * byteCount, true, true))
|
||||||
assertEquals(5u * bc, cursor.position)
|
assertEquals(5 * byteCount, cursor.position)
|
||||||
cursor.seekStart(bc)
|
cursor.seekStart(byteCount)
|
||||||
assertEquals("AB", cursor.read(2u * bc, true, true))
|
assertEquals("AB", cursor.read(2 * byteCount, true, true))
|
||||||
assertEquals(3u * bc, cursor.position)
|
assertEquals(3 * byteCount, cursor.position)
|
||||||
|
|
||||||
cursor.seekStart(bc)
|
cursor.seekStart(byteCount)
|
||||||
assertEquals("AB", cursor.read(4u * bc, true, false))
|
assertEquals("AB", cursor.read(4 * byteCount, true, false))
|
||||||
assertEquals(4u * bc, cursor.position)
|
assertEquals(4 * byteCount, cursor.position)
|
||||||
cursor.seekStart(bc)
|
cursor.seekStart(byteCount)
|
||||||
assertEquals("AB", cursor.read(2u * bc, true, false))
|
assertEquals("AB", cursor.read(2 * byteCount, true, false))
|
||||||
assertEquals(3u * bc, cursor.position)
|
assertEquals(3 * byteCount, cursor.position)
|
||||||
|
|
||||||
cursor.seekStart(bc)
|
cursor.seekStart(byteCount)
|
||||||
assertEquals("AB\u0000ÿ", cursor.read(4u * bc, false, true))
|
assertEquals("AB\u0000ÿ", cursor.read(4 * byteCount, false, true))
|
||||||
assertEquals(5u * bc, cursor.position)
|
assertEquals(5 * byteCount, cursor.position)
|
||||||
|
|
||||||
cursor.seekStart(bc)
|
cursor.seekStart(byteCount)
|
||||||
assertEquals("AB\u0000ÿ", cursor.read(4u * bc, false, false))
|
assertEquals("AB\u0000ÿ", cursor.read(4 * byteCount, false, false))
|
||||||
assertEquals(5u * bc, cursor.position)
|
assertEquals(5 * byteCount, cursor.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -308,13 +328,13 @@ abstract class CursorTests {
|
|||||||
|
|
||||||
val cursor = createCursor(bytes, endianness)
|
val cursor = createCursor(bytes, endianness)
|
||||||
|
|
||||||
val buf = cursor.seek(2).buffer(4u)
|
val buf = cursor.seek(2).buffer(4)
|
||||||
|
|
||||||
assertEquals(6u, cursor.position)
|
assertEquals(6, cursor.position)
|
||||||
assertEquals(4u, buf.size)
|
assertEquals(4, buf.size)
|
||||||
assertEquals(3u, buf.getU8(0u))
|
assertEquals(3u, buf.getU8(0))
|
||||||
assertEquals(4u, buf.getU8(1u))
|
assertEquals(4u, buf.getU8(1))
|
||||||
assertEquals(5u, buf.getU8(2u))
|
assertEquals(5u, buf.getU8(2))
|
||||||
assertEquals(6u, buf.getU8(3u))
|
assertEquals(6u, buf.getU8(3))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package world.phantasmal.lib.cursor
|
package world.phantasmal.lib.cursor
|
||||||
|
|
||||||
import world.phantasmal.lib.Endianness
|
import world.phantasmal.lib.Endianness
|
||||||
|
import world.phantasmal.lib.buffer.Buffer
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -18,15 +19,15 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
private fun simple_WritableCursor_properties_and_invariants(endianness: Endianness) {
|
private fun simple_WritableCursor_properties_and_invariants(endianness: Endianness) {
|
||||||
val cursor = createCursor(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), 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.writeU8(99u).writeU8(99u).writeU8(99u).writeU8(99u)
|
||||||
cursor.seek(-1)
|
cursor.seek(-1)
|
||||||
|
|
||||||
assertEquals(cursor.position + cursor.bytesLeft, cursor.size)
|
assertEquals(cursor.position + cursor.bytesLeft, cursor.size)
|
||||||
assertEquals(10u, cursor.size)
|
assertEquals(10, cursor.size)
|
||||||
assertEquals(3u, cursor.position)
|
assertEquals(3, cursor.position)
|
||||||
assertEquals(7u, cursor.bytesLeft)
|
assertEquals(7, cursor.bytesLeft)
|
||||||
assertEquals(endianness, cursor.endianness)
|
assertEquals(endianness, cursor.endianness)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +84,9 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
cursor.write(expectedNumber1)
|
cursor.write(expectedNumber1)
|
||||||
cursor.write(expectedNumber2)
|
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(expectedNumber1, cursor.read())
|
||||||
assertEquals(expectedNumber2, cursor.read())
|
assertEquals(expectedNumber2, cursor.read())
|
||||||
@ -106,23 +107,23 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
cursor.writeF32(1337.9001f)
|
cursor.writeF32(1337.9001f)
|
||||||
cursor.writeF32(103.502f)
|
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
|
// 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).
|
// they're backed by numbers (64-bit floats).
|
||||||
assertTrue(abs(1337.9001f - cursor.f32()) < 0.001)
|
assertTrue(abs(1337.9001f - cursor.f32()) < 0.001)
|
||||||
assertTrue(abs(103.502f - cursor.f32()) < 0.001)
|
assertTrue(abs(103.502f - cursor.f32()) < 0.001)
|
||||||
|
|
||||||
assertEquals(8u, cursor.position)
|
assertEquals(8, cursor.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun writeU8Array() {
|
fun writeU8Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = u8Array(n)
|
val arr = u8Array(n)
|
||||||
IntArray(n.toInt()) { arr[it].toInt() }
|
IntArray(n) { arr[it].toInt() }
|
||||||
}
|
}
|
||||||
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
||||||
writeU8Array(UByteArray(a.size) { a[it].toUByte() })
|
writeU8Array(UByteArray(a.size) { a[it].toUByte() })
|
||||||
@ -134,9 +135,9 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun writeU16Array() {
|
fun writeU16Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = u16Array(n)
|
val arr = u16Array(n)
|
||||||
IntArray(n.toInt()) { arr[it].toInt() }
|
IntArray(n) { arr[it].toInt() }
|
||||||
}
|
}
|
||||||
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
||||||
writeU16Array(UShortArray(a.size) { a[it].toUShort() })
|
writeU16Array(UShortArray(a.size) { a[it].toUShort() })
|
||||||
@ -148,9 +149,9 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun writeU32Array() {
|
fun writeU32Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
val arr = u32Array(n)
|
val arr = u32Array(n)
|
||||||
IntArray(n.toInt()) { arr[it].toInt() }
|
IntArray(n) { arr[it].toInt() }
|
||||||
}
|
}
|
||||||
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
||||||
writeU32Array(UIntArray(a.size) { a[it].toUInt() })
|
writeU32Array(UIntArray(a.size) { a[it].toUInt() })
|
||||||
@ -162,7 +163,7 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun writeI32Array() {
|
fun writeI32Array() {
|
||||||
val read: Cursor.(UInt) -> IntArray = { n ->
|
val read: Cursor.(Int) -> IntArray = { n ->
|
||||||
i32Array(n)
|
i32Array(n)
|
||||||
}
|
}
|
||||||
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
val write: WritableCursor.(IntArray) -> Unit = { a ->
|
||||||
@ -175,7 +176,7 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
|
|
||||||
private fun testIntegerArrayWrite(
|
private fun testIntegerArrayWrite(
|
||||||
byteCount: Int,
|
byteCount: Int,
|
||||||
read: Cursor.(UInt) -> IntArray,
|
read: Cursor.(Int) -> IntArray,
|
||||||
write: WritableCursor.(IntArray) -> Unit,
|
write: WritableCursor.(IntArray) -> Unit,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
) {
|
) {
|
||||||
@ -185,16 +186,42 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
val cursor = createCursor(ByteArray(20 * byteCount), endianness)
|
val cursor = createCursor(ByteArray(20 * byteCount), endianness)
|
||||||
|
|
||||||
cursor.write(testArray1)
|
cursor.write(testArray1)
|
||||||
assertEquals(10u * byteCount.toUInt(), cursor.position)
|
assertEquals(10 * byteCount, cursor.position)
|
||||||
|
|
||||||
cursor.write(testArray2)
|
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(testArray1.contentEquals(cursor.read(10)))
|
||||||
assertTrue(testArray2.contentEquals(cursor.read(10u)))
|
assertTrue(testArray2.contentEquals(cursor.read(10)))
|
||||||
assertEquals(20u * byteCount.toUInt(), cursor.position)
|
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
|
@Test
|
||||||
@ -208,10 +235,11 @@ abstract class WritableCursorTests : CursorTests() {
|
|||||||
|
|
||||||
cursor.writeU32(1u).writeU32(2u).writeU32(3u).writeU32(4u)
|
cursor.writeU32(1u).writeU32(2u).writeU32(3u).writeU32(4u)
|
||||||
cursor.seek(-8)
|
cursor.seek(-8)
|
||||||
val newCursor = cursor.take(8u)
|
val newCursor = cursor.take(8)
|
||||||
|
|
||||||
assertEquals(8u, newCursor.size)
|
assertEquals(16, cursor.position)
|
||||||
assertEquals(0u, newCursor.position)
|
assertEquals(8, newCursor.size)
|
||||||
|
assertEquals(0, newCursor.position)
|
||||||
assertEquals(3u, newCursor.u32())
|
assertEquals(3u, newCursor.u32())
|
||||||
assertEquals(4u, newCursor.u32())
|
assertEquals(4u, newCursor.u32())
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,20 @@ package world.phantasmal.lib.buffer
|
|||||||
|
|
||||||
import org.khronos.webgl.ArrayBuffer
|
import org.khronos.webgl.ArrayBuffer
|
||||||
import org.khronos.webgl.DataView
|
import org.khronos.webgl.DataView
|
||||||
|
import org.khronos.webgl.Int8Array
|
||||||
import org.khronos.webgl.Uint8Array
|
import org.khronos.webgl.Uint8Array
|
||||||
import world.phantasmal.lib.Endianness
|
import world.phantasmal.lib.Endianness
|
||||||
import world.phantasmal.lib.ZERO_U16
|
import world.phantasmal.lib.ZERO_U16
|
||||||
|
|
||||||
actual class Buffer private constructor(
|
actual class Buffer private constructor(
|
||||||
private var arrayBuffer: ArrayBuffer,
|
private var arrayBuffer: ArrayBuffer,
|
||||||
size: UInt,
|
size: Int,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
) {
|
) {
|
||||||
private var dataView = DataView(arrayBuffer)
|
private var dataView = DataView(arrayBuffer)
|
||||||
private var littleEndian = endianness == Endianness.Little
|
private var littleEndian = endianness == Endianness.Little
|
||||||
|
|
||||||
actual var size: UInt = size
|
actual var size: Int = size
|
||||||
set(value) {
|
set(value) {
|
||||||
ensureCapacity(value)
|
ensureCapacity(value)
|
||||||
field = value
|
field = value
|
||||||
@ -26,54 +27,54 @@ actual class Buffer private constructor(
|
|||||||
littleEndian = value == Endianness.Little
|
littleEndian = value == Endianness.Little
|
||||||
}
|
}
|
||||||
|
|
||||||
actual val capacity: UInt
|
actual val capacity: Int
|
||||||
get() = arrayBuffer.byteLength.toUInt()
|
get() = arrayBuffer.byteLength
|
||||||
|
|
||||||
actual fun getU8(offset: UInt): UByte {
|
actual fun getU8(offset: Int): UByte {
|
||||||
checkOffset(offset, 1u)
|
checkOffset(offset, 1)
|
||||||
return dataView.getUint8(offset.toInt()).toUByte()
|
return dataView.getUint8(offset).toUByte()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getU16(offset: UInt): UShort {
|
actual fun getU16(offset: Int): UShort {
|
||||||
checkOffset(offset, 2u)
|
checkOffset(offset, 2)
|
||||||
return dataView.getUint16(offset.toInt(), littleEndian).toUShort()
|
return dataView.getUint16(offset, littleEndian).toUShort()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getU32(offset: UInt): UInt {
|
actual fun getU32(offset: Int): UInt {
|
||||||
checkOffset(offset, 4u)
|
checkOffset(offset, 4)
|
||||||
return dataView.getUint32(offset.toInt(), littleEndian).toUInt()
|
return dataView.getUint32(offset, littleEndian).toUInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getI8(offset: UInt): Byte {
|
actual fun getI8(offset: Int): Byte {
|
||||||
checkOffset(offset, 1u)
|
checkOffset(offset, 1)
|
||||||
return dataView.getInt8(offset.toInt())
|
return dataView.getInt8(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getI16(offset: UInt): Short {
|
actual fun getI16(offset: Int): Short {
|
||||||
checkOffset(offset, 2u)
|
checkOffset(offset, 2)
|
||||||
return dataView.getInt16(offset.toInt(), littleEndian)
|
return dataView.getInt16(offset, littleEndian)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getI32(offset: UInt): Int {
|
actual fun getI32(offset: Int): Int {
|
||||||
checkOffset(offset, 4u)
|
checkOffset(offset, 4)
|
||||||
return dataView.getInt32(offset.toInt(), littleEndian)
|
return dataView.getInt32(offset, littleEndian)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getF32(offset: UInt): Float {
|
actual fun getF32(offset: Int): Float {
|
||||||
checkOffset(offset, 4u)
|
checkOffset(offset, 4)
|
||||||
return dataView.getFloat32(offset.toInt(), littleEndian)
|
return dataView.getFloat32(offset, littleEndian)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getStringUtf16(
|
actual fun getStringUtf16(
|
||||||
offset: UInt,
|
offset: Int,
|
||||||
maxByteLength: UInt,
|
maxByteLength: Int,
|
||||||
nullTerminated: Boolean,
|
nullTerminated: Boolean,
|
||||||
): String =
|
): String =
|
||||||
buildString {
|
buildString {
|
||||||
val len = maxByteLength / 2u
|
val len = maxByteLength / 2
|
||||||
|
|
||||||
for (i in 0u until len) {
|
for (i in 0 until len) {
|
||||||
val codePoint = getU16(offset + i * 2u)
|
val codePoint = getU16(offset + i * 2)
|
||||||
|
|
||||||
if (nullTerminated && codePoint == ZERO_U16) {
|
if (nullTerminated && codePoint == ZERO_U16) {
|
||||||
break
|
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)
|
checkOffset(offset, size)
|
||||||
return fromArrayBuffer(
|
return fromArrayBuffer(
|
||||||
arrayBuffer.slice(offset.toInt(), (offset + size).toInt()),
|
arrayBuffer.slice(offset, (offset + size)),
|
||||||
endianness
|
endianness
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setU8(offset: Int, value: UByte): Buffer {
|
||||||
* Writes an unsigned 8-bit integer at the given offset.
|
checkOffset(offset, 1)
|
||||||
*/
|
dataView.setUint8(offset, value.toByte())
|
||||||
actual fun setU8(offset: UInt, value: UByte): Buffer {
|
|
||||||
checkOffset(offset, 1u)
|
|
||||||
dataView.setUint8(offset.toInt(), value.toByte())
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setU16(offset: Int, value: UShort): Buffer {
|
||||||
* Writes an unsigned 16-bit integer at the given offset.
|
checkOffset(offset, 2)
|
||||||
*/
|
dataView.setUint16(offset, value.toShort(), littleEndian)
|
||||||
actual fun setU16(offset: UInt, value: UShort): Buffer {
|
|
||||||
checkOffset(offset, 2u)
|
|
||||||
dataView.setUint16(offset.toInt(), value.toShort(), littleEndian)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setU32(offset: Int, value: UInt): Buffer {
|
||||||
* Writes an unsigned 32-bit integer at the given offset.
|
checkOffset(offset, 4)
|
||||||
*/
|
dataView.setUint32(offset, value.toInt(), littleEndian)
|
||||||
actual fun setU32(offset: UInt, value: UInt): Buffer {
|
|
||||||
checkOffset(offset, 4u)
|
|
||||||
dataView.setUint32(offset.toInt(), value.toInt(), littleEndian)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setI8(offset: Int, value: Byte): Buffer {
|
||||||
* Writes a signed 8-bit integer at the given offset.
|
checkOffset(offset, 1)
|
||||||
*/
|
dataView.setInt8(offset, value)
|
||||||
actual fun setI8(offset: UInt, value: Byte): Buffer {
|
|
||||||
checkOffset(offset, 1u)
|
|
||||||
dataView.setInt8(offset.toInt(), value)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setI16(offset: Int, value: Short): Buffer {
|
||||||
* Writes a signed 16-bit integer at the given offset.
|
checkOffset(offset, 2)
|
||||||
*/
|
dataView.setInt16(offset, value, littleEndian)
|
||||||
actual fun setI16(offset: UInt, value: Short): Buffer {
|
|
||||||
checkOffset(offset, 2u)
|
|
||||||
dataView.setInt16(offset.toInt(), value, littleEndian)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setI32(offset: Int, value: Int): Buffer {
|
||||||
* Writes a signed 32-bit integer at the given offset.
|
checkOffset(offset, 4)
|
||||||
*/
|
dataView.setInt32(offset, value, littleEndian)
|
||||||
actual fun setI32(offset: UInt, value: Int): Buffer {
|
|
||||||
checkOffset(offset, 4u)
|
|
||||||
dataView.setInt32(offset.toInt(), value, littleEndian)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun setF32(offset: Int, value: Float): Buffer {
|
||||||
* Writes a 32-bit floating point number at the given offset.
|
checkOffset(offset, 4)
|
||||||
*/
|
dataView.setFloat32(offset, value, littleEndian)
|
||||||
actual fun setF32(offset: UInt, value: Float): Buffer {
|
|
||||||
checkOffset(offset, 4u)
|
|
||||||
dataView.setFloat32(offset.toInt(), value, littleEndian)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
actual fun zero(): Buffer =
|
||||||
* Writes 0 bytes to the entire buffer.
|
fill(0)
|
||||||
*/
|
|
||||||
actual fun zero(): Buffer {
|
actual fun fill(value: Byte): Buffer {
|
||||||
(Uint8Array(arrayBuffer).asDynamic()).fill(0)
|
(Int8Array(arrayBuffer).asDynamic()).fill(value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether we can read [size] bytes at [offset].
|
* Checks whether we can read [size] bytes at [offset].
|
||||||
*/
|
*/
|
||||||
private fun checkOffset(offset: UInt, size: UInt) {
|
private fun checkOffset(offset: Int, size: Int) {
|
||||||
require(offset + size <= this.size) {
|
require(offset >= 0 && offset + size <= this.size) {
|
||||||
"Offset $offset is out of bounds."
|
"Offset $offset is out of bounds."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,16 +154,16 @@ actual class Buffer private constructor(
|
|||||||
/**
|
/**
|
||||||
* Reallocates the underlying ArrayBuffer if necessary.
|
* Reallocates the underlying ArrayBuffer if necessary.
|
||||||
*/
|
*/
|
||||||
private fun ensureCapacity(minNewSize: UInt) {
|
private fun ensureCapacity(minNewSize: Int) {
|
||||||
if (minNewSize > capacity) {
|
if (minNewSize > capacity) {
|
||||||
var newSize = if (capacity == 0u) minNewSize else capacity;
|
var newSize = if (capacity == 0) minNewSize else capacity;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
newSize *= 2u;
|
newSize *= 2;
|
||||||
} while (newSize < minNewSize);
|
} while (newSize < minNewSize);
|
||||||
|
|
||||||
val newBuffer = ArrayBuffer(newSize.toInt());
|
val newBuffer = ArrayBuffer(newSize);
|
||||||
Uint8Array(newBuffer).set(Uint8Array(arrayBuffer, 0, size.toInt()));
|
Uint8Array(newBuffer).set(Uint8Array(arrayBuffer, 0, size));
|
||||||
arrayBuffer = newBuffer;
|
arrayBuffer = newBuffer;
|
||||||
dataView = DataView(arrayBuffer);
|
dataView = DataView(arrayBuffer);
|
||||||
}
|
}
|
||||||
@ -191,19 +171,21 @@ actual class Buffer private constructor(
|
|||||||
|
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun withCapacity(
|
actual fun withCapacity(
|
||||||
initialCapacity: UInt,
|
initialCapacity: Int,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
): Buffer =
|
): 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 {
|
actual fun fromByteArray(array: ByteArray, endianness: Endianness): Buffer {
|
||||||
val arrayBuffer = ArrayBuffer(array.size)
|
val arrayBuffer = ArrayBuffer(array.size)
|
||||||
Uint8Array(arrayBuffer).set(array.toTypedArray())
|
Int8Array(arrayBuffer).set(array.toTypedArray())
|
||||||
return Buffer(arrayBuffer, array.size.toUInt(), endianness)
|
return Buffer(arrayBuffer, array.size, endianness)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromArrayBuffer(arrayBuffer: ArrayBuffer, endianness: Endianness): Buffer {
|
fun fromArrayBuffer(arrayBuffer: ArrayBuffer, endianness: Endianness): Buffer =
|
||||||
return Buffer(arrayBuffer, arrayBuffer.byteLength.toUInt(), endianness)
|
Buffer(arrayBuffer, arrayBuffer.byteLength, endianness)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package world.phantasmal.lib.cursor
|
|||||||
import org.khronos.webgl.ArrayBuffer
|
import org.khronos.webgl.ArrayBuffer
|
||||||
import org.khronos.webgl.DataView
|
import org.khronos.webgl.DataView
|
||||||
import world.phantasmal.lib.Endianness
|
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.
|
* 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(
|
class ArrayBufferCursor(
|
||||||
buffer: ArrayBuffer,
|
buffer: ArrayBuffer,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
offset: UInt = 0u,
|
offset: Int = 0,
|
||||||
size: UInt = buffer.byteLength.toUInt() - offset,
|
size: Int = buffer.byteLength - offset,
|
||||||
) : AbstractArrayBufferCursor(endianness, offset) {
|
) : AbstractWritableCursor(offset) {
|
||||||
override val backingBuffer = buffer
|
private var littleEndian: Boolean = endianness == Endianness.Little
|
||||||
override val dv = DataView(buffer, 0, buffer.byteLength)
|
private val backingBuffer = buffer
|
||||||
|
private val dv = DataView(buffer)
|
||||||
|
|
||||||
override var size: UInt = size
|
override var size: Int = size
|
||||||
set(value) {
|
set(value) {
|
||||||
require(size <= backingBuffer.byteLength.toUInt() - offset)
|
require(size <= backingBuffer.byteLength - offset)
|
||||||
field = value
|
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 offset = offset + position
|
||||||
val wrapper = ArrayBufferCursor(backingBuffer, endianness, offset, size)
|
val wrapper = ArrayBufferCursor(backingBuffer, endianness, offset, size)
|
||||||
this.position += size
|
this.position += size
|
||||||
return wrapper
|
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)
|
||||||
|
@ -6,7 +6,7 @@ import io.ktor.client.features.json.serializer.*
|
|||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import org.w3c.dom.PopStateEvent
|
import org.w3c.dom.PopStateEvent
|
||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
@ -47,7 +47,7 @@ private fun init(): Disposable {
|
|||||||
val basePath = window.location.origin +
|
val basePath = window.location.origin +
|
||||||
(if (pathname.lastOrNull() == '/') pathname.dropLast(1) else pathname)
|
(if (pathname.lastOrNull() == '/') pathname.dropLast(1) else pathname)
|
||||||
|
|
||||||
val scope = CoroutineScope(Job())
|
val scope = CoroutineScope(SupervisorJob())
|
||||||
disposer.add(disposable { scope.cancel() })
|
disposer.add(disposable { scope.cancel() })
|
||||||
|
|
||||||
disposer.add(
|
disposer.add(
|
||||||
|
@ -5,7 +5,7 @@ import kotlinx.coroutines.launch
|
|||||||
import org.w3c.files.File
|
import org.w3c.files.File
|
||||||
import world.phantasmal.core.*
|
import world.phantasmal.core.*
|
||||||
import world.phantasmal.lib.Endianness
|
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.Quest
|
||||||
import world.phantasmal.lib.fileFormats.quest.parseBinDatToQuest
|
import world.phantasmal.lib.fileFormats.quest.parseBinDatToQuest
|
||||||
import world.phantasmal.observable.value.Val
|
import world.phantasmal.observable.value.Val
|
||||||
@ -17,7 +17,7 @@ import world.phantasmal.webui.readFile
|
|||||||
|
|
||||||
class QuestEditorToolbarController(
|
class QuestEditorToolbarController(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
private val questEditorStore: QuestEditorStore
|
private val questEditorStore: QuestEditorStore,
|
||||||
) : Controller(scope) {
|
) : Controller(scope) {
|
||||||
private val _resultDialogVisible = mutableVal(false)
|
private val _resultDialogVisible = mutableVal(false)
|
||||||
private val _result = mutableVal<PwResult<*>?>(null)
|
private val _result = mutableVal<PwResult<*>?>(null)
|
||||||
@ -46,11 +46,9 @@ class QuestEditorToolbarController(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val binBuffer = readFile(bin)
|
|
||||||
val datBuffer = readFile(dat)
|
|
||||||
val parseResult = parseBinDatToQuest(
|
val parseResult = parseBinDatToQuest(
|
||||||
ArrayBufferCursor(binBuffer, Endianness.Little),
|
readFile(bin).cursor(Endianness.Little),
|
||||||
ArrayBufferCursor(datBuffer, Endianness.Little)
|
readFile(dat).cursor(Endianness.Little),
|
||||||
)
|
)
|
||||||
setResult(parseResult)
|
setResult(parseResult)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user