Fix set compresion, added nbt and snbt(wip) lib
This commit is contained in:
56 files changed
+1064
-544
No files matched your search
@@ -14,7 +14,7 @@ kotlin {
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project(":common"))
|
||||
api(project(":common"))
|
||||
}
|
||||
|
||||
jvmMain.dependencies {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/5
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common.ByteOrder
|
||||
import cn.rtast.libmc.common.BytesBuffer
|
||||
|
||||
public class BytesBufferNBTInput(override val order: ByteOrder, private val buffer: BytesBuffer) : NBTInput {
|
||||
override fun readByte(): Byte = buffer.readByte()
|
||||
override fun readBytes(count: Int): ByteArray = buffer.readBytes(count)
|
||||
}
|
||||
|
||||
public fun BytesBuffer.toNBTInput(order: ByteOrder = ByteOrder.BIG_ENDIAN): BytesBufferNBTInput =
|
||||
BytesBufferNBTInput(order, this)
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/5
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common.ByteOrder
|
||||
import cn.rtast.libmc.common.BytesBuffer
|
||||
|
||||
public class BytesBufferNBTOutput(
|
||||
override val order: ByteOrder,
|
||||
override val root: NBTTag.CompoundTag,
|
||||
private val buffer: BytesBuffer,
|
||||
) : NBTOutput {
|
||||
override fun writeByte(value: Byte): Unit = buffer.writeByte(value)
|
||||
override fun writeBytes(value: ByteArray): Unit = buffer.writeBytes(value)
|
||||
override fun toByteArray(): ByteArray = buffer.toByteArray()
|
||||
}
|
||||
|
||||
public fun BytesBuffer.toNBTOutput(
|
||||
root: NBTTag.CompoundTag,
|
||||
order: ByteOrder = ByteOrder.BIG_ENDIAN,
|
||||
): BytesBufferNBTOutput =
|
||||
BytesBufferNBTOutput(order, root, this)
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/27
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public enum class CompressionType(internal val compressor: NbtCompressor?) {
|
||||
Zlib(NbtCompressor.ZlibCompressor),
|
||||
Gzip(NbtCompressor.GZipCompressor),
|
||||
Raw(null)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/5
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public data class NBTCompound(val root: String, val element: NBTTag)
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
@DslMarker
|
||||
public annotation class NbtDsl
|
||||
|
||||
@NbtDsl
|
||||
public class CompoundBuilder {
|
||||
private val tags = mutableMapOf<String, NBTTag>()
|
||||
|
||||
|
||||
public infix fun String.byte(value: Byte) {
|
||||
tags[this] = NBTTag.ByteTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.short(value: Short) {
|
||||
tags[this] = NBTTag.ShortTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.int(value: Int) {
|
||||
tags[this] = NBTTag.IntTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.long(value: Long) {
|
||||
tags[this] = NBTTag.LongTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.float(value: Float) {
|
||||
tags[this] = NBTTag.FloatTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.double(value: Double) {
|
||||
tags[this] = NBTTag.DoubleTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.byteArray(value: ByteArray) {
|
||||
tags[this] = NBTTag.ByteArrayTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.intArray(value: IntArray) {
|
||||
tags[this] = NBTTag.IntArrayTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.longArray(value: LongArray) {
|
||||
tags[this] = NBTTag.LongArrayTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.string(value: String) {
|
||||
tags[this] = NBTTag.StringTag(value)
|
||||
}
|
||||
|
||||
public infix fun String.compound(block: CompoundBuilder.() -> Unit) {
|
||||
tags[this] = nbtCompound(block)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private fun _list(
|
||||
elementType: NBTType,
|
||||
block: NBTListBuilder.() -> Unit,
|
||||
): NBTTag.ListTag {
|
||||
val builder = NBTListBuilder(elementType)
|
||||
builder.block()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
public fun String.list(elementType: NBTType, block: NBTListBuilder.() -> Unit) {
|
||||
tags[this] = _list(elementType, block)
|
||||
}
|
||||
|
||||
public fun build(): NBTTag.CompoundTag = NBTTag.CompoundTag(tags)
|
||||
}
|
||||
|
||||
public fun nbtCompound(block: CompoundBuilder.() -> Unit): NBTTag.CompoundTag {
|
||||
val builder = CompoundBuilder()
|
||||
builder.block()
|
||||
return builder.build()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common.ByteOrder
|
||||
|
||||
public interface NBTInput {
|
||||
public val order: ByteOrder
|
||||
|
||||
public fun readByte(): Byte
|
||||
public fun readBytes(count: Int): ByteArray
|
||||
|
||||
public fun readLong(): Long = if (order == ByteOrder.BIG_ENDIAN) readLongBE() else readLongLE()
|
||||
public fun readFloat(): Float = if (order == ByteOrder.BIG_ENDIAN) readFloatBE() else readFloatLE()
|
||||
public fun readDouble(): Double = if (order == ByteOrder.BIG_ENDIAN) readDoubleBE() else readDoubleLE()
|
||||
public fun readShort(): Short = if (order == ByteOrder.BIG_ENDIAN) readShortBE() else readShortLE()
|
||||
public fun readInt(): Int = if (order == ByteOrder.BIG_ENDIAN) readIntBE() else readIntLE()
|
||||
|
||||
// big endian
|
||||
public fun readLongBE(): Long =
|
||||
(readIntBE().toLong() shl 32) or (readIntBE().toLong() and 0xFFFFFFFFL)
|
||||
|
||||
public fun readFloatBE(): Float = Float.fromBits(readIntBE())
|
||||
public fun readDoubleBE(): Double = Double.fromBits(readLongBE())
|
||||
public fun readShortBE(): Short {
|
||||
val b1 = readByte().toInt() and 0xFF
|
||||
val b2 = readByte().toInt() and 0xFF
|
||||
return ((b1 shl 8) or b2).toShort()
|
||||
}
|
||||
|
||||
public fun readIntBE(): Int {
|
||||
return ((readByte().toInt() and 0xFF) shl 24) or
|
||||
((readByte().toInt() and 0xFF) shl 16) or
|
||||
((readByte().toInt() and 0xFF) shl 8) or
|
||||
(readByte().toInt() and 0xFF)
|
||||
}
|
||||
|
||||
// little endian
|
||||
public fun readLongLE(): Long {
|
||||
return ((readByte().toInt() and 0xFF).toLong() shl 0) or
|
||||
((readByte().toInt() and 0xFF).toLong() shl 8) or
|
||||
((readByte().toInt() and 0xFF).toLong() shl 16) or
|
||||
((readByte().toInt() and 0xFF).toLong() shl 24) or
|
||||
((readByte().toInt() and 0xFF).toLong() shl 32) or
|
||||
((readByte().toInt() and 0xFF).toLong() shl 40) or
|
||||
((readByte().toInt() and 0xFF).toLong() shl 48) or
|
||||
(readByte().toInt() and 0xFF).toLong()
|
||||
}
|
||||
|
||||
public fun readFloatLE(): Float = Float.fromBits(readIntLE())
|
||||
|
||||
public fun readDoubleLE(): Double = Double.fromBits(readLongLE())
|
||||
|
||||
public fun readShortLE(): Short =
|
||||
((readByte().toInt() and 0xFF) or (readByte().toInt() and 0xFF shl 8)).toShort()
|
||||
|
||||
public fun readIntLE(): Int =
|
||||
((readByte().toInt() and 0xFF)) or
|
||||
((readByte().toInt() and 0xFF) shl 8) or
|
||||
((readByte().toInt() and 0xFF) shl 16) or
|
||||
((readByte().toInt() and 0xFF) shl 24)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
@NbtDsl
|
||||
public class NBTListBuilder(
|
||||
private val elementType: NBTType,
|
||||
) {
|
||||
private val elements = mutableListOf<NBTTag>()
|
||||
|
||||
private fun checkType(expected: NBTType) =
|
||||
require(elementType == expected) { "TAG_List element type must same: expected $elementType, got $expected" }
|
||||
|
||||
public fun byte(value: Byte) {
|
||||
checkType(NBTType.Byte)
|
||||
elements += NBTTag.ByteTag(value)
|
||||
}
|
||||
|
||||
public fun short(value: Short) {
|
||||
checkType(NBTType.Short)
|
||||
elements += NBTTag.ShortTag(value)
|
||||
}
|
||||
|
||||
public fun int(value: Int) {
|
||||
checkType(NBTType.Int)
|
||||
elements += NBTTag.IntTag(value)
|
||||
}
|
||||
|
||||
public fun long(value: Long) {
|
||||
checkType(NBTType.Long)
|
||||
elements += NBTTag.LongTag(value)
|
||||
}
|
||||
|
||||
public fun float(value: Float) {
|
||||
checkType(NBTType.Float)
|
||||
elements += NBTTag.FloatTag(value)
|
||||
}
|
||||
|
||||
public fun double(value: Double) {
|
||||
checkType(NBTType.Double)
|
||||
elements += NBTTag.DoubleTag(value)
|
||||
}
|
||||
|
||||
public fun string(value: String) {
|
||||
checkType(NBTType.String)
|
||||
elements += NBTTag.StringTag(value)
|
||||
}
|
||||
|
||||
public fun compound(block: CompoundBuilder.() -> Unit) {
|
||||
checkType(NBTType.Compound)
|
||||
elements += nbtCompound(block)
|
||||
}
|
||||
|
||||
public fun list(elementType: NBTType, block: NBTListBuilder.() -> Unit) {
|
||||
checkType(NBTType.List)
|
||||
val builder = NBTListBuilder(elementType)
|
||||
builder.block()
|
||||
elements += builder.build()
|
||||
}
|
||||
|
||||
public fun build(): NBTTag.ListTag = NBTTag.ListTag(elementType, elements)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common.ByteOrder
|
||||
|
||||
public interface NBTOutput {
|
||||
public val order: ByteOrder
|
||||
public val root: NBTTag.CompoundTag
|
||||
|
||||
public fun writeByte(value: Byte)
|
||||
public fun writeBytes(value: ByteArray)
|
||||
|
||||
public fun writeShort(value: Short): Unit =
|
||||
if (order == ByteOrder.BIG_ENDIAN) writeShortBE(value) else writeShortLE(value)
|
||||
|
||||
public fun writeInt(value: Int): Unit =
|
||||
if (order == ByteOrder.BIG_ENDIAN) writeIntBE(value) else writeIntLE(value)
|
||||
|
||||
public fun writeLong(value: Long): Unit =
|
||||
if (order == ByteOrder.BIG_ENDIAN) writeLongBE(value) else writeLongLE(value)
|
||||
|
||||
public fun writeFloat(value: Float): Unit =
|
||||
if (order == ByteOrder.BIG_ENDIAN) writeFloatBE(value) else writeFloatLE(value)
|
||||
|
||||
public fun writeDouble(value: Double): Unit =
|
||||
if (order == ByteOrder.BIG_ENDIAN) writeDoubleBE(value) else writeDoubleLE(value)
|
||||
|
||||
// big endian
|
||||
public fun writeShortBE(value: Short) {
|
||||
writeByte(((value.toInt() shr 8) and 0xFF).toByte())
|
||||
writeByte((value.toInt() and 0xFF).toByte())
|
||||
}
|
||||
|
||||
public fun writeIntBE(value: Int) {
|
||||
writeByte(((value shr 24) and 0xFF).toByte())
|
||||
writeByte(((value shr 16) and 0xFF).toByte())
|
||||
writeByte(((value shr 8) and 0xFF).toByte())
|
||||
writeByte((value and 0xFF).toByte())
|
||||
}
|
||||
|
||||
public fun writeLongBE(value: Long) {
|
||||
writeByte(((value shr 56).toInt() and 0xFF).toByte())
|
||||
writeByte(((value shr 48).toInt() and 0xFF).toByte())
|
||||
writeByte(((value shr 40).toInt() and 0xFF).toByte())
|
||||
writeByte(((value shr 32).toInt() and 0xFF).toByte())
|
||||
writeByte(((value shr 24).toInt() and 0xFF).toByte())
|
||||
writeByte(((value shr 16).toInt() and 0xFF).toByte())
|
||||
writeByte(((value shr 8).toInt() and 0xFF).toByte())
|
||||
writeByte((value.toInt() and 0xFF).toByte())
|
||||
}
|
||||
|
||||
public fun writeFloatBE(value: Float) {
|
||||
writeIntBE(value.toBits())
|
||||
}
|
||||
|
||||
public fun writeDoubleBE(value: Double) {
|
||||
writeLongBE(value.toBits())
|
||||
}
|
||||
|
||||
// Little Endian
|
||||
public fun writeShortLE(value: Short) {
|
||||
writeByte((value.toInt() and 0xFF).toByte())
|
||||
writeByte(((value.toInt() shr 8) and 0xFF).toByte())
|
||||
}
|
||||
|
||||
public fun writeIntLE(value: Int) {
|
||||
writeByte((value and 0xFF).toByte())
|
||||
writeByte(((value shr 8) and 0xFF).toByte())
|
||||
writeByte(((value shr 16) and 0xFF).toByte())
|
||||
writeByte(((value shr 24) and 0xFF).toByte())
|
||||
}
|
||||
|
||||
public fun writeLongLE(value: Long) {
|
||||
writeByte(((value and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 8) and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 16) and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 24) and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 32) and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 40) and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 48) and 0xFF).toInt()).toByte())
|
||||
writeByte((((value shr 56) and 0xFF).toInt()).toByte())
|
||||
}
|
||||
|
||||
public fun writeFloatLE(value: Float): Unit = writeIntLE(value.toBits())
|
||||
|
||||
public fun writeDoubleLE(value: Double): Unit = writeLongLE(value.toBits())
|
||||
|
||||
|
||||
public fun toByteArray(): ByteArray
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public fun NBTInput.readStringTag(): String {
|
||||
val length = readShort().toInt() and 0xFFFF
|
||||
val bytes = readBytes(length)
|
||||
return bytes.decodeToString()
|
||||
}
|
||||
|
||||
public fun NBTInput.readListTag(): NBTTag.ListTag {
|
||||
val elementTypeId = readByte().toInt() and 0xFF
|
||||
val elementType = NBTType.fromId(elementTypeId)
|
||||
val length = readInt()
|
||||
val list = ArrayList<NBTTag>(length)
|
||||
repeat(length) { list += readTagPayload(elementType) }
|
||||
return NBTTag.ListTag(elementType, list)
|
||||
}
|
||||
|
||||
public fun NBTInput.readCompoundTag(): NBTTag.CompoundTag {
|
||||
val map = LinkedHashMap<String, NBTTag>()
|
||||
while (true) {
|
||||
val typeId = readByte().toInt() and 0xFF
|
||||
if (typeId == 0) break // TAG_End
|
||||
val name = readStringTag()
|
||||
val type = NBTType.fromId(typeId)
|
||||
val payload = readTagPayload(type)
|
||||
map[name] = payload
|
||||
}
|
||||
return NBTTag.CompoundTag(map)
|
||||
}
|
||||
|
||||
public fun NBTInput.readTagPayload(type: NBTType): NBTTag =
|
||||
when (type) {
|
||||
NBTType.Byte -> NBTTag.ByteTag(readByte())
|
||||
NBTType.Short -> NBTTag.ShortTag(readShort())
|
||||
NBTType.Int -> NBTTag.IntTag(readInt())
|
||||
NBTType.Long -> NBTTag.LongTag(readLong())
|
||||
NBTType.Float -> NBTTag.FloatTag(readFloat())
|
||||
NBTType.Double -> NBTTag.DoubleTag(readDouble())
|
||||
NBTType.String -> NBTTag.StringTag(readStringTag())
|
||||
NBTType.ByteArray -> NBTTag.ByteArrayTag(readBytes(readInt()))
|
||||
NBTType.IntArray -> NBTTag.IntArrayTag(IntArray(readInt()) { readInt() })
|
||||
NBTType.LongArray -> NBTTag.LongArrayTag(LongArray(readInt()) { readLong() })
|
||||
NBTType.List -> readListTag()
|
||||
NBTType.Compound -> readCompoundTag()
|
||||
NBTType.End -> error("TAG_End no payload")
|
||||
}
|
||||
|
||||
public fun NBTInput.readCompound(): NBTTag {
|
||||
val map = LinkedHashMap<String, NBTTag>()
|
||||
while (true) {
|
||||
val typeId = readByte().toInt()
|
||||
val type = NBTType.fromId(typeId)
|
||||
if (type == NBTType.End) break
|
||||
val nameLen = readShort().toInt() and 0xFFFF
|
||||
val nameBytes = readBytes(nameLen)
|
||||
val name = nameBytes.decodeToString()
|
||||
val value = readTagPayload(type)
|
||||
map[name] = value
|
||||
}
|
||||
return NBTTag.ListTag(NBTType.Compound, map.values.toMutableList())
|
||||
}
|
||||
|
||||
public fun NBTInput.readRootCompound(): NBTCompound {
|
||||
val rootType = NBTType.fromId(readByte().toInt())
|
||||
require(rootType == NBTType.Compound) { "Root tag must be TAG_Compound" }
|
||||
val nameLen = readShort().toInt() and 0xFFFF
|
||||
val name = readBytes(nameLen).decodeToString()
|
||||
val root = readCompound()
|
||||
return NBTCompound(name, root)
|
||||
}
|
||||
|
||||
public fun NBTInput.readNetworkCompound(): NBTCompound {
|
||||
val root = readCompound()
|
||||
return NBTCompound("", root)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public sealed class NBTTag(public val type: NBTType) {
|
||||
public data class ByteTag(val value: Byte) : NBTTag(NBTType.Byte)
|
||||
public data class ShortTag(val value: Short) : NBTTag(NBTType.Short)
|
||||
public data class IntTag(val value: Int) : NBTTag(NBTType.Int)
|
||||
public data class LongTag(val value: Long) : NBTTag(NBTType.Long)
|
||||
public data class FloatTag(val value: Float) : NBTTag(NBTType.Float)
|
||||
public data class DoubleTag(val value: Double) : NBTTag(NBTType.Double)
|
||||
public data class StringTag(val value: String) : NBTTag(NBTType.String)
|
||||
public data class CompoundTag(val value: MutableMap<String, NBTTag>) : NBTTag(NBTType.Compound)
|
||||
public data class ListTag(val elementType: NBTType, val value: MutableList<NBTTag>) : NBTTag(NBTType.List) {
|
||||
public val length: Int get() = value.size
|
||||
}
|
||||
|
||||
public data class IntArrayTag(val value: IntArray) : NBTTag(NBTType.IntArray) {
|
||||
public val length: Int get() = value.size
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
other as IntArrayTag
|
||||
if (!value.contentEquals(other.value)) return false
|
||||
if (length != other.length) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = value.contentHashCode()
|
||||
result = 31 * result + length
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
public data class LongArrayTag(val value: LongArray) : NBTTag(NBTType.LongArray) {
|
||||
public val length: Int get() = value.size
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
other as LongArrayTag
|
||||
if (!value.contentEquals(other.value)) return false
|
||||
if (length != other.length) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = value.contentHashCode()
|
||||
result = 31 * result + length
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public data class ByteArrayTag(val value: ByteArray) : NBTTag(NBTType.ByteArray) {
|
||||
public val length: Int get() = value.size
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
other as ByteArrayTag
|
||||
if (!value.contentEquals(other.value)) return false
|
||||
if (length != other.length) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = value.contentHashCode()
|
||||
result = 31 * result + length
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public enum class NBTType(public val id: Byte) {
|
||||
End(0), Byte(1), Short(2), Int(3),
|
||||
Long(4), Float(5), Double(6), ByteArray(7),
|
||||
String(8), List(9), Compound(10),
|
||||
IntArray(11), LongArray(12);
|
||||
|
||||
public companion object {
|
||||
public fun fromId(id: Int): NBTType =
|
||||
entries.firstOrNull { it.id.toInt() == id } ?: error("Unknown NBT type id: $id")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
|
||||
public fun NBTOutput.writeStringTag(value: String) {
|
||||
val bytes = value.encodeToByteArray()
|
||||
writeShort(bytes.size.toShort())
|
||||
writeBytes(bytes)
|
||||
}
|
||||
|
||||
public fun NBTOutput.writeTagPayload(tag: NBTTag) {
|
||||
when (tag) {
|
||||
is NBTTag.ByteTag -> writeByte(tag.value)
|
||||
is NBTTag.ShortTag -> writeShort(tag.value)
|
||||
is NBTTag.IntTag -> writeInt(tag.value)
|
||||
is NBTTag.LongTag -> writeLong(tag.value)
|
||||
is NBTTag.FloatTag -> writeInt(tag.value.toBits())
|
||||
is NBTTag.DoubleTag -> writeLong(tag.value.toBits())
|
||||
is NBTTag.StringTag -> writeStringTag(tag.value)
|
||||
|
||||
is NBTTag.ByteArrayTag -> {
|
||||
writeInt(tag.length)
|
||||
writeBytes(tag.value)
|
||||
}
|
||||
|
||||
is NBTTag.IntArrayTag -> {
|
||||
writeInt(tag.length)
|
||||
tag.value.forEach { writeInt(it) }
|
||||
}
|
||||
|
||||
is NBTTag.LongArrayTag -> {
|
||||
writeInt(tag.length)
|
||||
tag.value.forEach { writeLong(it) }
|
||||
}
|
||||
|
||||
is NBTTag.ListTag -> {
|
||||
writeByte(tag.elementType.id)
|
||||
writeInt(tag.length)
|
||||
tag.value.forEach { writeTagPayload(it) }
|
||||
}
|
||||
|
||||
is NBTTag.CompoundTag -> {
|
||||
for ((name, value) in tag.value) {
|
||||
writeByte(value.type.id)
|
||||
writeStringTag(name)
|
||||
writeTagPayload(value)
|
||||
}
|
||||
writeByte(NBTType.End.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun NBTOutput.writeRootNBTCompound(name: String = ""): ByteArray {
|
||||
writeByte(NBTType.Compound.id)
|
||||
writeStringTag(name)
|
||||
writeTagPayload(root)
|
||||
return toByteArray()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/1/25
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common.gzipCompress
|
||||
import cn.rtast.libmc.common.gzipDecompress
|
||||
import cn.rtast.libmc.common.zlibCompress
|
||||
import cn.rtast.libmc.common.zlibDecompress
|
||||
|
||||
public sealed interface NbtCompressor {
|
||||
public fun compress(input: ByteArray): ByteArray
|
||||
public fun decompress(input: ByteArray): ByteArray
|
||||
|
||||
public object GZipCompressor : NbtCompressor {
|
||||
override fun compress(input: ByteArray): ByteArray = input.gzipCompress()
|
||||
override fun decompress(input: ByteArray): ByteArray = input.gzipDecompress()
|
||||
}
|
||||
|
||||
public object ZlibCompressor : NbtCompressor {
|
||||
override fun compress(input: ByteArray): ByteArray = input.zlibCompress()
|
||||
override fun decompress(input: ByteArray): ByteArray = input.zlibDecompress()
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/4
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common._Buffer
|
||||
import cn.rtast.libmc.common.readVarInt
|
||||
|
||||
public class NbtReader(
|
||||
private val buffer: _Buffer,
|
||||
private val variant: NbtVariant = NbtVariant.JAVA,
|
||||
) {
|
||||
private fun readShort(): Short = buffer.readShort(variant.order)
|
||||
private fun readInt(): Int = buffer.readInt(variant.order)
|
||||
private fun readLong(): Long = buffer.readLong(variant.order)
|
||||
private fun readFloat(): Float = Float.fromBits(readInt())
|
||||
private fun readDouble(): Double = Double.fromBits(readLong())
|
||||
|
||||
private fun readString(): String {
|
||||
val length = if (variant.isNetwork) buffer.readVarInt() else readShort().toInt() and 0xFFFF
|
||||
if (length == 0) return ""
|
||||
return buffer.readBytes(length).decodeToString()
|
||||
}
|
||||
|
||||
private fun readTagType(): Byte = if (variant.isNetwork) buffer.readVarInt().toByte() else buffer.readByte()
|
||||
|
||||
public fun readRoot(): Pair<String, NbtCompound> {
|
||||
val type = readTagType()
|
||||
require(type == NBTType.COMPOUND) { "Expected Compound Tag (10), got $type" }
|
||||
val name = readString()
|
||||
val root = readCompound()
|
||||
return name to root
|
||||
}
|
||||
|
||||
private fun readCompound(): NbtCompound {
|
||||
val map = mutableMapOf<String, NBTElement>()
|
||||
while (true) {
|
||||
val type = readTagType()
|
||||
if (type == NBTType.END) break
|
||||
|
||||
val name = readString()
|
||||
map[name] = readPayload(type)
|
||||
}
|
||||
return NbtCompound(map)
|
||||
}
|
||||
|
||||
private fun readList(): NbtList {
|
||||
val elementType = readTagType()
|
||||
val size = if (variant.isNetwork) buffer.readVarInt() else readInt()
|
||||
if (size <= 0) return NbtList(elementType, emptyList())
|
||||
val list = mutableListOf<NBTElement>()
|
||||
(0 until size).forEach { _ -> list.add(readPayload(elementType)) }
|
||||
return NbtList(elementType, list)
|
||||
}
|
||||
|
||||
private fun readPayload(type: Byte): NBTElement {
|
||||
return when (type) {
|
||||
NBTType.BYTE -> NbtByte(buffer.readByte())
|
||||
NBTType.SHORT -> NbtShort(readShort())
|
||||
NBTType.INT -> {
|
||||
val value = if (variant.isNetwork) buffer.readVarInt().decodeZigZag() else readInt()
|
||||
NbtInt(value)
|
||||
}
|
||||
|
||||
NBTType.LONG -> NbtLong(readLong())
|
||||
NBTType.FLOAT -> NbtFloat(readFloat())
|
||||
NBTType.DOUBLE -> NbtDouble(readDouble())
|
||||
NBTType.BYTE_ARRAY -> {
|
||||
val len = if (variant.isNetwork) buffer.readVarInt() else readInt()
|
||||
NbtByteArray(buffer.readBytes(len))
|
||||
}
|
||||
|
||||
NBTType.STRING -> NbtString(readString())
|
||||
NBTType.LIST -> readList()
|
||||
NBTType.COMPOUND -> readCompound()
|
||||
NBTType.INT_ARRAY -> {
|
||||
val len = if (variant.isNetwork) buffer.readVarInt() else readInt()
|
||||
val array = IntArray(len) { readInt() }
|
||||
NbtIntArray(array)
|
||||
}
|
||||
|
||||
NBTType.LONG_ARRAY -> {
|
||||
val len = if (variant.isNetwork) buffer.readVarInt() else readInt()
|
||||
val array = LongArray(len) { readLong() }
|
||||
NbtLongArray(array)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown Tag type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/4
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public sealed interface NBTElement {
|
||||
public val typeId: NBTTypeID
|
||||
}
|
||||
|
||||
public data class NbtByte(val value: Byte) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.BYTE
|
||||
}
|
||||
|
||||
public data class NbtShort(val value: Short) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.SHORT
|
||||
}
|
||||
|
||||
public data class NbtInt(val value: Int) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.INT
|
||||
}
|
||||
|
||||
public data class NbtLong(val value: Long) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.LONG
|
||||
}
|
||||
|
||||
public data class NbtFloat(val value: Float) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.FLOAT
|
||||
}
|
||||
|
||||
public data class NbtDouble(val value: Double) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.DOUBLE
|
||||
}
|
||||
|
||||
public data class NbtByteArray(val value: ByteArray) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.BYTE_ARRAY
|
||||
override fun equals(other: Any?): Boolean = other is NbtByteArray && value.contentEquals(other.value)
|
||||
override fun hashCode(): Int = value.contentHashCode()
|
||||
}
|
||||
|
||||
public data class NbtString(val value: String) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.STRING
|
||||
}
|
||||
|
||||
public data class NbtList(val elementType: Byte, val elements: List<NBTElement>) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.LIST
|
||||
}
|
||||
|
||||
public data class NbtCompound(val map: Map<String, NBTElement>) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.COMPOUND
|
||||
}
|
||||
|
||||
public data class NbtIntArray(val value: IntArray) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.INT_ARRAY
|
||||
override fun equals(other: Any?): Boolean = other is NbtIntArray && value.contentEquals(other.value)
|
||||
override fun hashCode(): Int = value.contentHashCode()
|
||||
}
|
||||
|
||||
public data class NbtLongArray(val value: LongArray) : NBTElement {
|
||||
override val typeId: NBTTypeID = NBTType.LONG_ARRAY
|
||||
override fun equals(other: Any?): Boolean = other is NbtLongArray && value.contentEquals(other.value)
|
||||
override fun hashCode(): Int = value.contentHashCode()
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/4
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
public typealias NBTTypeID = Byte
|
||||
|
||||
public object NBTType {
|
||||
public const val END: NBTTypeID = 0
|
||||
public const val BYTE: NBTTypeID = 1
|
||||
public const val SHORT: NBTTypeID = 2
|
||||
public const val INT: NBTTypeID = 3
|
||||
public const val LONG: NBTTypeID = 4
|
||||
public const val FLOAT: NBTTypeID = 5
|
||||
public const val DOUBLE: NBTTypeID = 6
|
||||
public const val BYTE_ARRAY: NBTTypeID = 7
|
||||
public const val STRING: NBTTypeID = 8
|
||||
public const val LIST: NBTTypeID = 9
|
||||
public const val COMPOUND: NBTTypeID = 10
|
||||
public const val INT_ARRAY: NBTTypeID = 11
|
||||
public const val LONG_ARRAY: NBTTypeID = 12
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/4
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
import cn.rtast.libmc.common.ByteOrder
|
||||
|
||||
public enum class NbtVariant(
|
||||
public val order: ByteOrder,
|
||||
public val isNetwork: Boolean,
|
||||
) {
|
||||
JAVA(ByteOrder.BIG_ENDIAN, isNetwork = false),
|
||||
BEDROCK_DISK(ByteOrder.LITTLE_ENDIAN, isNetwork = false),
|
||||
BEDROCK_NETWORK(ByteOrder.LITTLE_ENDIAN, isNetwork = true)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/4
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.nbt
|
||||
|
||||
internal fun Int.decodeZigZag(): Int = (this ushr 1) xor -(this and 1)
|
||||
internal fun Int.encodeZigZag(): Int = (this shl 1) xor (this shr 31)
|
||||
Reference in New Issue
Block a user