Fix set compresion, added nbt and snbt(wip) lib

This commit is contained in:
2026-09-05 16:35:55 +08:00
parent 60324028ee
commit 5e6e0ca5b9
56 files changed
+1064 -544

No files matched your search

@@ -12,3 +12,7 @@ public expect fun ByteArray.zlibDecompress(): ByteArray
public expect fun ByteArray.zlibDecompress(expectedSize: Int): ByteArray
public expect fun ByteArray.zlibCompress(): ByteArray
public expect fun ByteArray.gzipCompress(): ByteArray
public expect fun ByteArray.gzipDecompress(): ByteArray
@@ -10,6 +10,7 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.zip.Deflater
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.zip.Inflater
public actual fun ByteArray.zlibDecompress(): ByteArray {
@@ -29,28 +30,18 @@ public actual fun ByteArray.zlibDecompress(): ByteArray {
}
}
private fun ByteArray.gzipDecompress(): ByteArray {
if (isEmpty()) return byteArrayOf()
ByteArrayInputStream(this).use { bais ->
GZIPInputStream(bais).use { gzis ->
val outputStream = ByteArrayOutputStream(this.size * 2)
val buffer = ByteArray(1024)
var len: Int
while (gzis.read(buffer).also { len = it } != -1) {
outputStream.write(buffer, 0, len)
}
return outputStream.toByteArray()
}
}
}
public actual fun ByteArray.zlibDecompress(expectedSize: Int): ByteArray {
val inflater = Inflater()
inflater.setInput(this)
val result = ByteArray(expectedSize)
var totalRead = 0
try {
val resultLength = inflater.inflate(result)
check(resultLength == expectedSize) { "Decompression failed: expected $expectedSize bytes, but got $resultLength" }
while (!inflater.finished() && totalRead < expectedSize) {
val read = inflater.inflate(result, totalRead, expectedSize - totalRead)
if (read == 0) break
totalRead += read
}
check(totalRead == expectedSize) { "Decompression failed: expected $expectedSize bytes, but got $totalRead" }
return result
} finally {
inflater.end()
@@ -61,8 +52,22 @@ public actual fun ByteArray.zlibCompress(): ByteArray {
val deflater = Deflater()
deflater.setInput(this)
deflater.finish()
val output = ByteArray(this.size + 64)
val compressedSize = deflater.deflate(output)
val bos = ByteArrayOutputStream(this.size)
val buffer = ByteArray(1024)
while (!deflater.finished()) {
val count = deflater.deflate(buffer)
if (count > 0) bos.write(buffer, 0, count)
}
deflater.end()
return output.copyOf(compressedSize)
return bos.toByteArray()
}
public actual fun ByteArray.gzipCompress(): ByteArray {
val bos = ByteArrayOutputStream()
GZIPOutputStream(bos).use { it.write(this) }
return bos.toByteArray()
}
public actual fun ByteArray.gzipDecompress(): ByteArray {
return GZIPInputStream(ByteArrayInputStream(this)).use { it.readBytes() }
}
@@ -1,104 +1,131 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
* Copyright © 2025-2026 RTAkland
* Open Source Under Apache-2.0 License
* https://www.apache.org/licenses/LICENSE-2.0
*/
@file:OptIn(ExperimentalForeignApi::class, UnsafeNumber::class)
@file:OptIn(ExperimentalForeignApi::class)
package cn.rtast.libmc.common
import kotlinx.cinterop.*
import platform.posix.u_longVar
import platform.zlib.*
private const val ENABLE_ZLIB_GZIP_HEADER = 15 + 32
private const val MAX_WBITS = 15
private const val DEFAULT_BUFFER_SIZE = 4096
public actual fun ByteArray.zlibDecompress(): ByteArray {
if (isEmpty()) return ByteArray(0)
return memScoped {
val stream = alloc<z_stream>()
stream.zalloc = null
stream.zfree = null
stream.opaque = null
private inline fun ByteArray.processDecompress(initStream: (CPointer<z_stream>) -> Unit): ByteArray = memScoped {
val stream = alloc<z_stream>()
stream.zalloc = null
stream.zfree = null
stream.opaque = null
initStream(stream.ptr)
stream.next_in = this@processDecompress.refTo(0).getPointer(this).reinterpret()
stream.avail_in = this@processDecompress.size.toUInt()
val output = mutableListOf<Byte>()
val tempBuffer = ByteArray(DEFAULT_BUFFER_SIZE)
val initResult = inflateInit2_(
stream.ptr,
ENABLE_ZLIB_GZIP_HEADER,
ZLIB_VERSION,
sizeOf<z_stream>().toInt()
)
check(initResult == Z_OK) { "inflateInit2_ failed with code: $initResult" }
try {
do {
stream.next_out = tempBuffer.refTo(0).getPointer(this).reinterpret()
stream.avail_out = DEFAULT_BUFFER_SIZE.toUInt()
val result = inflate(stream.ptr, Z_NO_FLUSH)
check(result == Z_OK || result == Z_STREAM_END) { "inflate error: $result" }
val bytesDecompressed = DEFAULT_BUFFER_SIZE - stream.avail_out.toInt()
output.addAll(tempBuffer.take(bytesDecompressed))
val inputPinned = this@zlibDecompress.pin()
try {
stream.next_in = inputPinned.addressOf(0).reinterpret()
stream.avail_in = this@zlibDecompress.size.toUInt()
val bufferSize = 4096
val tempBuffer = ByteArray(bufferSize)
val tempPinned = tempBuffer.pin()
val output = ArrayList<Byte>(this@zlibDecompress.size * 3)
try {
var result: Int
do {
stream.next_out = tempPinned.addressOf(0).reinterpret()
stream.avail_out = bufferSize.toUInt()
result = inflate(stream.ptr, Z_NO_FLUSH)
check(result == Z_OK || result == Z_STREAM_END) { "inflate error: $result" }
val bytesDecompressed = bufferSize - stream.avail_out.toInt()
for (i in 0 until bytesDecompressed) {
output.add(tempBuffer[i])
}
if (result == Z_STREAM_END) break
} while (stream.avail_in > 0u || stream.avail_out == 0u)
} finally {
tempPinned.unpin()
}
inflateEnd(stream.ptr)
return@memScoped output.toByteArray()
} finally {
inputPinned.unpin()
}
if (result == Z_STREAM_END) break
} while (stream.avail_out == 0u)
} finally {
inflateEnd(stream.ptr)
}
return output.toByteArray()
}
public actual fun ByteArray.zlibDecompress(expectedSize: Int): ByteArray {
private inline fun ByteArray.processCompress(initStream: (CPointer<z_stream>) -> Unit): ByteArray = memScoped {
val stream = alloc<z_stream>()
stream.zalloc = null
stream.zfree = null
stream.opaque = null
initStream(stream.ptr)
stream.next_in = this@processCompress.refTo(0).getPointer(this).reinterpret()
stream.avail_in = this@processCompress.size.toUInt()
val output = mutableListOf<Byte>()
val tempBuffer = ByteArray(DEFAULT_BUFFER_SIZE)
try {
do {
stream.next_out = tempBuffer.refTo(0).getPointer(this).reinterpret()
stream.avail_out = DEFAULT_BUFFER_SIZE.toUInt()
val result = deflate(stream.ptr, Z_FINISH)
check(result == Z_OK || result == Z_STREAM_END) { "deflate error: $result" }
val have = DEFAULT_BUFFER_SIZE - stream.avail_out.toInt()
output.addAll(tempBuffer.take(have))
} while (stream.avail_out == 0u)
} finally {
deflateEnd(stream.ptr)
}
return output.toByteArray()
}
public actual fun ByteArray.zlibDecompress(expectedSize: Int): ByteArray = memScoped {
val stream = alloc<z_stream>()
stream.zalloc = null
stream.zfree = null
stream.opaque = null
check(inflateInit_(stream.ptr, ZLIB_VERSION, sizeOf<z_stream>().toInt()) == Z_OK) { "inflateInit_ failed" }
val result = ByteArray(expectedSize)
if (this.isEmpty()) return result
memScoped {
val destLen = alloc<u_longVar>()
destLen.value = expectedSize.toUInt()
val res = uncompress(
result.refTo(0).getPointer(this).reinterpret(),
destLen.ptr,
this@zlibDecompress.refTo(0).getPointer(this).reinterpret(),
this@zlibDecompress.size.toUInt()
)
check(res == Z_OK) { "zlib uncompress failed with error code: $res" }
try {
stream.next_in = this@zlibDecompress.refTo(0).getPointer(this).reinterpret()
stream.avail_in = this@zlibDecompress.size.toUInt()
stream.next_out = result.refTo(0).getPointer(this).reinterpret()
stream.avail_out = expectedSize.toUInt()
var totalRead = 0
while (stream.avail_in > 0u && totalRead < expectedSize) {
val status = inflate(stream.ptr, Z_NO_FLUSH)
check(status == Z_STREAM_END || status == Z_OK) { "inflate failed with status: $status" }
val decompressedThisTurn = expectedSize - totalRead - stream.avail_out.toInt()
totalRead += decompressedThisTurn
if (status == Z_STREAM_END) break
}
check(totalRead == expectedSize) {
"Decompression failed: expected $expectedSize bytes, but got $totalRead"
}
return result
} finally {
inflateEnd(stream.ptr)
}
return result
}
public actual fun ByteArray.zlibCompress(): ByteArray {
if (this.isEmpty()) return byteArrayOf()
val maxCompressedLen = compressBound(this.size.toUInt()).toInt()
val output = ByteArray(maxCompressedLen)
memScoped {
val destLen = alloc<u_longVar>()
destLen.value = maxCompressedLen.toUInt()
val res = compress(
output.refTo(0).getPointer(this).reinterpret(),
destLen.ptr,
this@zlibCompress.refTo(0).getPointer(this).reinterpret(),
this@zlibCompress.size.toUInt()
)
check(res == Z_OK) { "zlib compress failed with error code: $res" }
return output.copyOf(destLen.value.toInt())
}
public actual fun ByteArray.gzipCompress(): ByteArray = processCompress { ptr ->
check(
deflateInit2_(
ptr, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
16 + MAX_WBITS, 8, Z_DEFAULT_STRATEGY, ZLIB_VERSION, sizeOf<z_stream>().toInt()
) == Z_OK
) { "deflateInit2_ failed" }
}
public actual fun ByteArray.gzipDecompress(): ByteArray = processDecompress { ptr ->
check(
inflateInit2_(
ptr, 16 + MAX_WBITS, ZLIB_VERSION, sizeOf<z_stream>().toInt()
) == Z_OK
) { "inflateInit2_ failed" }
}
public actual fun ByteArray.zlibCompress(): ByteArray = processCompress { ptr ->
check(
deflateInit_(
ptr, Z_DEFAULT_COMPRESSION, ZLIB_VERSION, sizeOf<z_stream>().toInt()
) == Z_OK
) { "deflateInit_ failed" }
}
public actual fun ByteArray.zlibDecompress(): ByteArray = processDecompress { ptr ->
check(
inflateInit_(
ptr, ZLIB_VERSION, sizeOf<z_stream>().toInt()
) == Z_OK
) { "inflateInit_ failed" }
}
+1 -1
View File
@@ -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)
+1
View File
@@ -19,6 +19,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
api(project(":common"))
api(project(":nbt"))
api(libs.kotlinx.serialization.core)
api(libs.kotlinx.serialization.json)
api(libs.kotlinx.coroutines)
@@ -9,9 +9,7 @@ package cn.rtast.libmc.protocol.client
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.packet.configuration.*
import cn.rtast.libmc.protocol.packet.login.ClientboundDisconnectLoginPacket
import cn.rtast.libmc.protocol.packet.login.ClientboundLoginSuccessPacket
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
import cn.rtast.libmc.protocol.packet.login.*
import cn.rtast.libmc.protocol.packet.play.*
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
@@ -21,53 +19,75 @@ internal class InternalPacketDispatcher(private val client: MinecraftClient) {
suspend fun handleIncomingPackets(packet: MinecraftPacket) {
this.dispatchEvent(packet)
when (packet) {
is ClientboundLoginPacket -> this.handleLoginPackets(packet)
is ClientboundConfigurationPacket -> this.handleConfigurationPackets(packet)
is ClientboundPlayPacket -> this.handlePlayPackets(packet)
}
}
private fun handleLoginPackets(packet: ClientboundLoginPacket) {
when (packet) {
is ClientboundDisconnectLoginPacket -> {
println("Login denied: ${packet.reason}")
client.close()
}
is ClientboundSetCompressionPacket -> client.networkChannel.setCompression(packet.threshold)
is ClientboundLoginSuccessPacket -> {
client.networkChannel.sendPacket(ServerboundLoginAcknowledgedPacket)
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
}
}
}
is ClientboundDisconnectLoginPacket -> {
println("Login denied: ${packet.reason}")
// close()
private fun handleConfigurationPackets(packet: ClientboundConfigurationPacket) {
when (packet) {
is ClientboundCookieRequestPacket -> {
// TODO
}
is ClientboundSelectKnownPacksPacket -> {
client.networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList())) // TODO empty resource packs list
}
is ClientboundPingPacket -> client.networkChannel.sendPacket(ServerboundPongPacket(packet.id))
is ClientboundKeepAliveConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundKeepAliveConfigurationPacket(packet.id))
}
is ClientboundFinishConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket)
client.stateMachine.transitionTo(ProtocolState.PLAY)
is ClientboundCustomPayloadPacket -> {
// TODO
}
is ClientboundDisconnectConfigurationPacket -> {
println("Configuration disconnected: ${packet.reason}")
// close()
}
is ClientboundLoginPlayPacket -> {
println("Successfully joined world! Entity ID: ${packet.entityId}")
ClientboundFinishConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket)
client.stateMachine.transitionTo(ProtocolState.PLAY)
}
is ClientboundKeepAlivePlayPacket -> {
client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(id = packet.id))
is ClientboundKeepAliveConfigurationPacket -> client.networkChannel.sendPacket(
ServerboundKeepAliveConfigurationPacket(packet.id)
)
is ClientboundPingConfigurationPacket -> client.networkChannel.sendPacket(
ServerboundPongConfigurationPacket(packet.id)
)
is ClientboundSelectKnownPacksPacket -> {
client.networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList())) // TODO empty resource packs list
}
}
}
private fun handlePlayPackets(packet: ClientboundPlayPacket) {
when (packet) {
is ClientboundDisconnectPlayPacket -> client.close()
is ClientboundKeepAlivePlayPacket -> client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(id = packet.id))
is ClientboundLoginPlayPacket -> println("Successfully joined world Entity ID: ${packet.entityId}")
is ClientboundPingPlayPacket -> client.networkChannel.sendPacket(ServerboundPongPlayPacket(packet.id))
is ClientboundPlayerChatMessagePacket -> {
println("Received player chat message $packet")
// TODO
}
is ClientboundStartConfigurationPacket -> {
ClientboundStartConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket)
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
}
is ClientboundDisconnectPlayPacket -> {
println("Disconnected from play session: ${packet.reason}")
// close()
}
}
}
}
@@ -10,6 +10,7 @@ import cn.rtast.libmc.common.*
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.client.ClientStateMachine
import cn.rtast.libmc.protocol.protocol.GameProtocols
import kotlin.concurrent.Volatile
internal class NetworkChannel(
private val host: String,
@@ -20,6 +21,8 @@ internal class NetworkChannel(
private var socket: Socket? = null
private var readChannel: ReadChannel? = null
private var writeChannel: WriteChannel? = null
@Volatile
private var threshold = -1
fun connect() {
@@ -37,46 +40,43 @@ internal class NetworkChannel(
val channel = requireNotNull(readChannel) { "ReadChannel not connected" }
val packetLength = channel.readVarInt()
val rawFrameBytes = channel.readBytes(packetLength)
val payloadBuf = if (threshold < 0) rawFrameBytes.wrap() else {
val frameBuf = rawFrameBytes.wrap()
val frameBuf = rawFrameBytes.wrap()
val payloadBuf = if (threshold < 0) frameBuf else {
val dataLength = frameBuf.readVarInt()
if (dataLength == 0) {
frameBuf.readBytes(frameBuf.remaining.toInt()).wrap()
} else frameBuf.readBytes(frameBuf.remaining.toInt()).zlibDecompress(dataLength).wrap()
val remainingBytes = frameBuf.readBytes(frameBuf.remaining.toInt())
if (dataLength == 0) remainingBytes.wrap() else remainingBytes.zlibDecompress(dataLength).wrap()
}
val currentState = stateMachine.currentState
val packetId = payloadBuf.readVarInt()
return GameProtocols.clientboundGameProtocols.getRegistry(currentState).decodePacket(packetId, payloadBuf)
val packet = GameProtocols.clientboundGameProtocols
.getRegistry(currentState)
.decodePacket(packetId, payloadBuf)
return packet
}
fun sendPacket(packet: MinecraftPacket) {
val channel = requireNotNull(writeChannel) { "WriteChannel not connected" }
val bodyBuffer = BytesBuffer()
GameProtocols.serverboundGameProtocols.getRegistry(stateMachine.currentState).encodePacket(bodyBuffer, packet)
val frameBuffer = BytesBuffer().apply {
if (threshold < 0) {
writeVarInt(bodyBuffer.size)
writeBuffer(bodyBuffer)
val uncompressedBodyBuf = BytesBuffer()
GameProtocols.serverboundGameProtocols
.getRegistry(stateMachine.currentState)
.encodePacket(uncompressedBodyBuf, packet)
val uncompressedData = uncompressedBodyBuf.toByteArray()
val frameBuffer = BytesBuffer()
if (threshold < 0) {
frameBuffer.writeVarInt(uncompressedData.size)
frameBuffer.writeBytes(uncompressedData)
} else {
val contentBuf = BytesBuffer()
if (uncompressedData.size < threshold) {
contentBuf.writeVarInt(0)
contentBuf.writeBytes(uncompressedData)
} else {
val uncompressedData = bodyBuffer.toByteArray()
if (uncompressedData.size < threshold) {
val contentBuf = BytesBuffer().apply {
writeVarInt(0)
writeBytes(uncompressedData)
}
writeVarInt(contentBuf.size)
writeBuffer(contentBuf)
} else {
val compressedData = uncompressedData.zlibCompress()
val contentBuf = BytesBuffer().apply {
writeVarInt(uncompressedData.size)
writeBytes(compressedData)
}
writeVarInt(contentBuf.size)
writeBuffer(contentBuf)
}
val compressedData = uncompressedData.zlibCompress()
contentBuf.writeVarInt(uncompressedData.size)
contentBuf.writeBytes(compressedData)
}
frameBuffer.writeVarInt(contentBuf.size)
frameBuffer.writeBuffer(contentBuf)
}
channel.writeFully(frameBuffer.toByteArray())
channel.flush()
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.packet.MinecraftPacket
public sealed interface ClientboundConfigurationPacket : MinecraftPacket
@@ -9,12 +9,11 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class ClientboundCookieRequestPacket(val key: Identifier) : MinecraftPacket {
public data class ClientboundCookieRequestPacket(val key: Identifier) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundCookieRequestPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundCookieRequestPacket) {
buffer.writeIdentifier(value.key)
@@ -9,12 +9,12 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class ClientboundCustomPayloadPacket(val channel: Identifier, val data: ByteArray) : MinecraftPacket {
public data class ClientboundCustomPayloadPacket(val channel: Identifier, val data: ByteArray) :
ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundCustomPayloadPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundCustomPayloadPacket) {
buffer.writeIdentifier(value.channel)
@@ -7,17 +7,16 @@
package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.protocol.util.readMinimalTextNbt
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ClientboundDisconnectConfigurationPacket(val reason: String) : MinecraftPacket {
public data class ClientboundDisconnectConfigurationPacket(val reason: NBTCompound) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket {
val reasonText = buffer.readMinimalTextNbt()
return ClientboundDisconnectConfigurationPacket(reason = reasonText)
return ClientboundDisconnectConfigurationPacket(reason = buffer.readNetworkNBTCompound())
}
}
}
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data object ClientboundFinishConfigurationPacket : MinecraftPacket,
public data object ClientboundFinishConfigurationPacket : ClientboundConfigurationPacket,
PacketCodec<ClientboundFinishConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundFinishConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundFinishConfigurationPacket {
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundKeepAliveConfigurationPacket(val id: Long) : MinecraftPacket {
public data class ClientboundKeepAliveConfigurationPacket(val id: Long) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundKeepAliveConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAliveConfigurationPacket) {
buffer.writeLong(value.id)
@@ -9,14 +9,13 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundPingPacket(val id: Int) : MinecraftPacket {
public companion object Codec : PacketCodec<ClientboundPingPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPingPacket) {
public data class ClientboundPingConfigurationPacket(val id: Int) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundPingConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPingConfigurationPacket) {
buffer.writeInt(value.id)
}
override fun decode(buffer: BytesBuffer): ClientboundPingPacket = ClientboundPingPacket(buffer.readInt())
override fun decode(buffer: BytesBuffer): ClientboundPingConfigurationPacket = ClientboundPingConfigurationPacket(buffer.readInt())
}
}
@@ -9,11 +9,10 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.common.writeVarInt
public data class ClientboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : MinecraftPacket {
public data class ClientboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundSelectKnownPacksPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSelectKnownPacksPacket) {
buffer.writeVarInt(value.knownPacks.size)
@@ -7,13 +7,11 @@
package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readMcString
import cn.rtast.libmc.common.writeMcString
import kotlinx.serialization.Serializable
@Serializable
public data class KnownPacks(val namespace: String, val id: String, val version: String) {
public companion object Codec : PacketCodec<KnownPacks> {
override fun encode(buffer: BytesBuffer, value: KnownPacks) {
@@ -11,12 +11,12 @@ import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ServerboundPongPacket(val id: Int) : MinecraftPacket {
public companion object Codec : PacketCodec<ServerboundPongPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundPongPacket) {
public data class ServerboundPongConfigurationPacket(val id: Int) : MinecraftPacket {
public companion object Codec : PacketCodec<ServerboundPongConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundPongConfigurationPacket) {
buffer.writeInt(value.id)
}
override fun decode(buffer: BytesBuffer): ServerboundPongPacket = ServerboundPongPacket(buffer.readInt())
override fun decode(buffer: BytesBuffer): ServerboundPongConfigurationPacket = ServerboundPongConfigurationPacket(buffer.readInt())
}
}
@@ -9,10 +9,9 @@ package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readMcString
public data class ClientboundDisconnectLoginPacket(val reason: String) : MinecraftPacket {
public data class ClientboundDisconnectLoginPacket(val reason: String) : ClientboundLoginPacket {
public companion object Codec : PacketCodec<ClientboundDisconnectLoginPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket {
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.packet.MinecraftPacket
public sealed interface ClientboundLoginPacket : MinecraftPacket
@@ -9,12 +9,12 @@ package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readUuid
import cn.rtast.libmc.protocol.profile.GameProfile
import kotlin.uuid.Uuid
public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) : MinecraftPacket {
public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) :
ClientboundLoginPacket {
public companion object Codec : PacketCodec<ClientboundLoginSuccessPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundLoginSuccessPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundLoginSuccessPacket {
@@ -9,11 +9,10 @@ package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.common.writeVarInt
public data class ClientboundSetCompressionPacket(val threshold: Int) : MinecraftPacket {
public data class ClientboundSetCompressionPacket(val threshold: Int) : ClientboundLoginPacket {
public companion object Codec : PacketCodec<ClientboundSetCompressionPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSetCompressionPacket) {
buffer.writeVarInt(value.threshold)
@@ -13,7 +13,6 @@ import cn.rtast.libmc.common.packet.MinecraftPacket
public data object ServerboundLoginAcknowledgedPacket : MinecraftPacket,
PacketCodec<ServerboundLoginAcknowledgedPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundLoginAcknowledgedPacket) {}
override fun decode(buffer: BytesBuffer): ServerboundLoginAcknowledgedPacket = throw UnsupportedOperationException()
}
@@ -7,17 +7,16 @@
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.protocol.util.readMinimalTextNbt
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ClientboundDisconnectPlayPacket(val reason: String) : MinecraftPacket {
public data class ClientboundDisconnectPlayPacket(val reason: NBTCompound) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundDisconnectPlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectPlayPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectPlayPacket {
val reasonText = buffer.readMinimalTextNbt()
return ClientboundDisconnectPlayPacket(reason = reasonText)
return ClientboundDisconnectPlayPacket(reason = buffer.readNetworkNBTCompound())
}
}
}
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundKeepAlivePlayPacket(val id: Long) : MinecraftPacket {
public data class ClientboundKeepAlivePlayPacket(val id: Long) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundKeepAlivePlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAlivePlayPacket) {
buffer.writeLong(value.id)
@@ -7,15 +7,10 @@
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.protocol.protocol.game.BlockPos
import cn.rtast.libmc.protocol.protocol.game.GameMode
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readBlockPos
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.*
public data class ClientboundLoginPlayPacket(
val entityId: Int,
@@ -41,7 +36,7 @@ public data class ClientboundLoginPlayPacket(
val seaLevel: Int,
val isOnlineMode: Boolean,
val enforceSecureChat: Boolean,
) : MinecraftPacket {
) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundLoginPlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundLoginPlayPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundLoginPlayPacket {
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundPingPlayPacket(val id: Int) : MinecraftPacket {
public data class ClientboundPingPlayPacket(val id: Int) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundPingPlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPingPlayPacket) {
buffer.writeInt(value.id)
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.packet.MinecraftPacket
public sealed interface ClientboundPlayPacket : MinecraftPacket
@@ -8,10 +8,13 @@
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.*
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.util.writeMinimalTextNbt
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import kotlin.uuid.Uuid
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Player_Chat_Message
*/
public data class ClientboundPlayerChatMessagePacket(
val globalIndex: Int,
val sender: Uuid,
@@ -21,13 +24,13 @@ public data class ClientboundPlayerChatMessagePacket(
val timestamp: Long,
val salt: Long,
val previousMessages: List<PreviousMessageEntry>,
val unsignedContent: String?,
val unsignedContent: NBTCompound?,
val filterType: ChatFilterType,
val filterMaskBits: LongArray?,
val chatType: Int,
val senderName: String,
val targetName: String?,
) : MinecraftPacket {
val senderName: NBTCompound,
val targetName: NBTCompound?,
) : ClientboundPlayPacket {
public enum class ChatFilterType(public val id: Int) {
PASS_THROUGH(0),
FULLY_FILTERED(1),
@@ -39,32 +42,22 @@ public data class ClientboundPlayerChatMessagePacket(
}
}
public data class PreviousMessageEntry(
val messageId: Int,
val signature: ByteArray?,
) {
public data class PreviousMessageEntry(val messageId: Int, val signature: ByteArray?) {
public companion object Codec : PacketCodec<PreviousMessageEntry> {
override fun encode(buffer: BytesBuffer, value: PreviousMessageEntry) {
buffer.writeVarInt(value.messageId)
if (value.messageId == 0) {
val sig = requireNotNull(value.signature) { "signature must be present when messageId is 0" }
require(sig.size == 256)
buffer.writeBytes(sig)
}
override fun encode(buffer: BytesBuffer, value: PreviousMessageEntry) {}
override fun decode(buffer: BytesBuffer): PreviousMessageEntry {
val messageId = buffer.readVarInt()
val signature = if (messageId == 0) buffer.readBytes(256) else null
return PreviousMessageEntry(messageId, signature)
}
override fun decode(buffer: BytesBuffer): PreviousMessageEntry = throw UnsupportedOperationException() // TODO
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as PreviousMessageEntry
if (messageId != other.messageId) return false
if (!signature.contentEquals(other.signature)) return false
return true
}
@@ -76,43 +69,46 @@ public data class ClientboundPlayerChatMessagePacket(
}
public companion object Codec : PacketCodec<ClientboundPlayerChatMessagePacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPlayerChatMessagePacket) {
buffer.writeVarInt(value.globalIndex)
buffer.writeUuid(value.sender)
buffer.writeVarInt(value.index)
val hasSignature = value.messageSignature != null
buffer.writeBoolean(hasSignature)
if (hasSignature) buffer.writeBytes(requireNotNull(value.messageSignature))
override fun encode(buffer: BytesBuffer, value: ClientboundPlayerChatMessagePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundPlayerChatMessagePacket {
val globalIndex = buffer.readVarInt()
val sender = buffer.readUuid()
val index = buffer.readVarInt()
val hasSignature = buffer.readBoolean()
val messageSignature = if (hasSignature) buffer.readBytes(256) else null
buffer.writeMcString(value.message)
buffer.writeLong(value.timestamp)
buffer.writeLong(value.salt)
val message = buffer.readMcString()
val timestamp = buffer.readLong()
val salt = buffer.readLong()
require(value.previousMessages.size == 20)
buffer.writeVarInt(value.previousMessages.size)
value.previousMessages.forEach { entry -> PreviousMessageEntry.encode(buffer, entry) }
val prevMessageCount = buffer.readVarInt()
val prevMessages = List(prevMessageCount) { PreviousMessageEntry.decode(buffer) }
val hasUnsignedContent = value.unsignedContent != null
buffer.writeBoolean(hasUnsignedContent)
value.unsignedContent?.let { buffer.writeMinimalTextNbt(it) }
val hasUnsignedContent = buffer.readBoolean()
val unsignedContent = if (hasUnsignedContent) buffer.readNetworkNBTCompound() else null // ?
buffer.writeVarInt(value.filterType.id)
if (value.filterType == ChatFilterType.PARTIALLY_FILTERED) {
val mask = requireNotNull(value.filterMaskBits)
buffer.writeVarInt(mask.size)
mask.forEach { buffer.writeLong(it) }
}
val filterTypeId = buffer.readVarInt()
val filterType = ChatFilterType.fromId(filterTypeId)
buffer.writeVarInt(value.chatType)
buffer.writeMinimalTextNbt(value.senderName)
val filterMaskBits = if (filterType == ChatFilterType.PARTIALLY_FILTERED) {
val bitSetLen = buffer.readVarInt()
LongArray(bitSetLen) { buffer.readLong() }
} else null
val hasTargetName = value.targetName != null
buffer.writeBoolean(hasTargetName)
value.targetName?.let { buffer.writeMinimalTextNbt(it) }
val chatType = buffer.readVarInt()
val senderName = buffer.readNetworkNBTCompound() // ?
val hasTargetName = buffer.readBoolean()
val targetName = if (hasTargetName) buffer.readNetworkNBTCompound() else null // ?
return ClientboundPlayerChatMessagePacket(
globalIndex, sender, index,
messageSignature, message,
timestamp, salt, prevMessages,
unsignedContent, filterType,
filterMaskBits, chatType,
senderName, targetName
)
}
override fun decode(buffer: BytesBuffer): ClientboundPlayerChatMessagePacket =
throw UnsupportedOperationException() // TODO
}
override fun equals(other: Any?): Boolean {
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data object ClientboundStartConfigurationPacket : MinecraftPacket,
public data object ClientboundStartConfigurationPacket : ClientboundPlayPacket,
PacketCodec<ClientboundStartConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundStartConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundStartConfigurationPacket {
@@ -25,7 +25,7 @@ internal object GameProtocols {
register(0x02, ClientboundDisconnectConfigurationPacket)
register(0x03, ClientboundFinishConfigurationPacket)
register(0x04, ClientboundKeepAliveConfigurationPacket)
register(0x05, ClientboundPingPacket)
register(0x05, ClientboundPingConfigurationPacket)
register(0x0e, ClientboundSelectKnownPacksPacket)
}
register(ProtocolState.LOGIN) {
@@ -34,6 +34,7 @@ internal object GameProtocols {
register(0x03, ClientboundSetCompressionPacket)
}
register(ProtocolState.PLAY) {
register(0x20, ClientboundDisconnectPlayPacket)
register(0x2c, ClientboundKeepAlivePlayPacket)
register(0x31, ClientboundLoginPlayPacket)
register(0x3d, ClientboundPingPlayPacket)
@@ -49,7 +50,7 @@ internal object GameProtocols {
register(ProtocolState.CONFIGURATION) {
register(0x03, ServerboundAckFinishConfigurationPacket)
register(0x04, ServerboundKeepAliveConfigurationPacket)
register(0x05, ServerboundPongPacket)
register(0x05, ServerboundPongConfigurationPacket)
register(0x07, ServerboundSelectKnownPacksPacket)
}
register(ProtocolState.LOGIN) {
@@ -17,7 +17,7 @@ public enum class GameMode(public val id: Byte) {
/**
* reserved
*/
Unknown(-99);
Unknown(-128);
public companion object {
public fun fromID(id: Byte): GameMode = entries.firstOrNull { it.id == id } ?: Unknown
@@ -7,19 +7,21 @@
package cn.rtast.libmc.protocol.protocol.game
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readMcString
import cn.rtast.libmc.common.writeMcString
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Identifier
*/
@JvmInline
public value class Identifier(public val full: String) {
public val namespace: String get() = if (full.contains(':')) full.substringBefore(':') else "minecraft"
public val path: String get() = if (full.contains(':')) full.substringAfter(':') else full
@Serializable
public value class Identifier internal constructor(public val raw: String) {
public val namespace: String get() = if (raw.contains(':')) raw.substringBefore(':') else "minecraft"
public val path: String get() = if (raw.contains(':')) raw.substringAfter(':') else raw
override fun toString(): String = "$namespace:$path"
@@ -0,0 +1,20 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.protocol.util
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.nbt.readNetworkCompound
import cn.rtast.libmc.nbt.readRootCompound
import cn.rtast.libmc.nbt.toNBTInput
internal fun BytesBuffer.readNBTCompound(): NBTCompound =
this.toNBTInput().readRootCompound()
internal fun BytesBuffer.readNetworkNBTCompound(): NBTCompound =
this.toNBTInput().readNetworkCompound()
@@ -1,43 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.libmc.protocol.util
import cn.rtast.libmc.common.BytesBuffer
/**
* tmp
*/
internal fun BytesBuffer.writeMinimalTextNbt(text: String) {
writeByte(0x0A)
writeByte(0x08)
val keyBytes = "text".encodeToByteArray()
writeShort(keyBytes.size.toShort())
writeBytes(keyBytes)
val valBytes = text.encodeToByteArray()
require(valBytes.size <= 32767)
writeShort(valBytes.size.toShort())
writeBytes(valBytes)
writeByte(0x00)
}
internal fun BytesBuffer.readMinimalTextNbt(): String {
val rootTagType = readByte().toInt()
if (rootTagType != 0x0A) return ""
var resultText = ""
while (true) {
val tagType = readByte().toInt()
if (tagType == 0x00) break
val keyLength = readShort().toInt()
val key = readBytes(keyLength).decodeToString()
if (tagType == 0x08 && key == "text") {
val valLength = readShort().toInt()
resultText = readBytes(valLength).decodeToString()
} else break
}
return resultText
}
@@ -8,6 +8,7 @@
package test
import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.packet.play.ClientboundLoginPlayPacket
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
@@ -19,6 +20,9 @@ class TestClient {
val cli = createMinecraftClient("127.0.0.1", 25565, "123")
cli.launch { cli.connect() }
cli.on<ClientboundLoginPlayPacket> {
println(it)
}
while (true) {
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(project(":common"))
api(project(":common"))
}
jvmMain.dependencies {
+29
View File
@@ -0,0 +1,29 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
explicitApi()
withSourcesJar()
linuxX64()
linuxArm64()
macosArm64()
mingwX64()
iosArm64()
iosSimulatorArm64()
jvm { compilerOptions.jvmTarget = JvmTarget.JVM_1_8 }
sourceSets {
commonMain.dependencies {
api(project(":common"))
}
jvmMain.dependencies {
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
}
}
}
+4 -3
View File
@@ -7,8 +7,9 @@ includeSubModule(":common")
includeSubModule(":mcping")
includeSubModule(":rconlib")
includeSubModule(":protocol")
//includeSubModule(":nbt")
includeSubModule(":nbt")
includeSubModule(":snbt")
fun includeSubModule(name: String) = include(name).also {
project(name).projectDir = file("libmc-${name.removePrefix(":")}")
fun includeSubModule(name: String, path: String? = null) = include(name).also {
project(name).projectDir = file(path ?: "libmc-${name.removePrefix(":")}")
}