diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/zlib.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/zlib.kt index 31a6615..fde436f 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/zlib.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/zlib.kt @@ -11,4 +11,8 @@ public expect fun ByteArray.zlibDecompress(): ByteArray public expect fun ByteArray.zlibDecompress(expectedSize: Int): ByteArray -public expect fun ByteArray.zlibCompress(): ByteArray \ No newline at end of file +public expect fun ByteArray.zlibCompress(): ByteArray + +public expect fun ByteArray.gzipCompress(): ByteArray + +public expect fun ByteArray.gzipDecompress(): ByteArray \ No newline at end of file diff --git a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/zlib.jvm.kt b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/zlib.jvm.kt index bafb6c6..887afa7 100644 --- a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/zlib.jvm.kt +++ b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/zlib.jvm.kt @@ -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() } } \ No newline at end of file diff --git a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/zlib.native.kt b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/zlib.native.kt index a6b2c80..e07c24c 100644 --- a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/zlib.native.kt +++ b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/zlib.native.kt @@ -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() - stream.zalloc = null - stream.zfree = null - stream.opaque = null +private inline fun ByteArray.processDecompress(initStream: (CPointer) -> Unit): ByteArray = memScoped { + val stream = alloc() + 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() + val tempBuffer = ByteArray(DEFAULT_BUFFER_SIZE) - val initResult = inflateInit2_( - stream.ptr, - ENABLE_ZLIB_GZIP_HEADER, - ZLIB_VERSION, - sizeOf().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(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) -> Unit): ByteArray = memScoped { + val stream = alloc() + 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() + 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() + stream.zalloc = null + stream.zfree = null + stream.opaque = null + check(inflateInit_(stream.ptr, ZLIB_VERSION, sizeOf().toInt()) == Z_OK) { "inflateInit_ failed" } val result = ByteArray(expectedSize) - if (this.isEmpty()) return result - memScoped { - val destLen = alloc() - 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() - 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().toInt() + ) == Z_OK + ) { "deflateInit2_ failed" } +} + +public actual fun ByteArray.gzipDecompress(): ByteArray = processDecompress { ptr -> + check( + inflateInit2_( + ptr, 16 + MAX_WBITS, ZLIB_VERSION, sizeOf().toInt() + ) == Z_OK + ) { "inflateInit2_ failed" } +} + +public actual fun ByteArray.zlibCompress(): ByteArray = processCompress { ptr -> + check( + deflateInit_( + ptr, Z_DEFAULT_COMPRESSION, ZLIB_VERSION, sizeOf().toInt() + ) == Z_OK + ) { "deflateInit_ failed" } +} + +public actual fun ByteArray.zlibDecompress(): ByteArray = processDecompress { ptr -> + check( + inflateInit_( + ptr, ZLIB_VERSION, sizeOf().toInt() + ) == Z_OK + ) { "inflateInit_ failed" } } \ No newline at end of file diff --git a/libmc-nbt/build.gradle.kts b/libmc-nbt/build.gradle.kts index 9cb79a5..fe2882f 100644 --- a/libmc-nbt/build.gradle.kts +++ b/libmc-nbt/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { sourceSets { commonMain.dependencies { - implementation(project(":common")) + api(project(":common")) } jvmMain.dependencies { diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/BytesBufferNBTInput.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/BytesBufferNBTInput.kt new file mode 100644 index 0000000..d085332 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/BytesBufferNBTInput.kt @@ -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) \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/BytesBufferNBTOutput.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/BytesBufferNBTOutput.kt new file mode 100644 index 0000000..bcb11d7 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/BytesBufferNBTOutput.kt @@ -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) \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/CompressionType.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/CompressionType.kt new file mode 100644 index 0000000..12e1e47 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/CompressionType.kt @@ -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) +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTCompound.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTCompound.kt new file mode 100644 index 0000000..825e595 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTCompound.kt @@ -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) \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTCompoundBuilder.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTCompoundBuilder.kt new file mode 100644 index 0000000..b32a8ed --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTCompoundBuilder.kt @@ -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() + + + 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() +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTInput.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTInput.kt new file mode 100644 index 0000000..8e6ac4a --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTInput.kt @@ -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) +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTListBuilder.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTListBuilder.kt new file mode 100644 index 0000000..d333112 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTListBuilder.kt @@ -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() + + 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) +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTOutput.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTOutput.kt new file mode 100644 index 0000000..47224fd --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTOutput.kt @@ -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 +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTReader.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTReader.kt new file mode 100644 index 0000000..2e078aa --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTReader.kt @@ -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(length) + repeat(length) { list += readTagPayload(elementType) } + return NBTTag.ListTag(elementType, list) +} + +public fun NBTInput.readCompoundTag(): NBTTag.CompoundTag { + val map = LinkedHashMap() + 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() + 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) +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTTag.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTTag.kt new file mode 100644 index 0000000..5e7d877 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTTag.kt @@ -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) : NBTTag(NBTType.Compound) + public data class ListTag(val elementType: NBTType, val value: MutableList) : 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 + } + } +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTType.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTType.kt new file mode 100644 index 0000000..3564ada --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTType.kt @@ -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") + } +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTWriter.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTWriter.kt new file mode 100644 index 0000000..3ab50a9 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NBTWriter.kt @@ -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() +} diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NbtCompressor.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NbtCompressor.kt new file mode 100644 index 0000000..afadd45 --- /dev/null +++ b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/NbtCompressor.kt @@ -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() + } +} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_reader.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_reader.kt deleted file mode 100644 index 4857e2a..0000000 --- a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_reader.kt +++ /dev/null @@ -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 { - 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() - 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() - (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") - } - } -} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_tags.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_tags.kt deleted file mode 100644 index 1601d61..0000000 --- a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_tags.kt +++ /dev/null @@ -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 { - override val typeId: NBTTypeID = NBTType.LIST -} - -public data class NbtCompound(val map: Map) : 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() -} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_type.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_type.kt deleted file mode 100644 index e87667c..0000000 --- a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_type.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_variant.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_variant.kt deleted file mode 100644 index c5860f9..0000000 --- a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/nbt_variant.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/varint.kt b/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/varint.kt deleted file mode 100644 index 21c4499..0000000 --- a/libmc-nbt/src/commonMain/kotlin/cn/rtast/libmc/nbt/varint.kt +++ /dev/null @@ -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) diff --git a/libmc-protocol/build.gradle.kts b/libmc-protocol/build.gradle.kts index d1ea2e5..7090571 100644 --- a/libmc-protocol/build.gradle.kts +++ b/libmc-protocol/build.gradle.kts @@ -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) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt index 6c13bf6..0287c97 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt @@ -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() - } } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt index 9096c58..959bc40 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt @@ -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() diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundConfigurationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundConfigurationPacket.kt new file mode 100644 index 0000000..c28b039 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundConfigurationPacket.kt @@ -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 \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCookieRequestPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCookieRequestPacket.kt index 3fa8a52..d2c1382 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCookieRequestPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCookieRequestPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundCookieRequestPacket) { buffer.writeIdentifier(value.key) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCustomPayloadPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCustomPayloadPacket.kt index af87f6e..5acc573 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCustomPayloadPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundCustomPayloadPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundCustomPayloadPacket) { buffer.writeIdentifier(value.channel) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundDisconnectConfigurationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundDisconnectConfigurationPacket.kt index cc7cb14..3b46bbf 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundDisconnectConfigurationPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundDisconnectConfigurationPacket.kt @@ -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 { 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()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundFinishConfigurationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundFinishConfigurationPacket.kt index d46e8a3..f7c9152 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundFinishConfigurationPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundFinishConfigurationPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundFinishConfigurationPacket) {} override fun decode(buffer: BytesBuffer): ClientboundFinishConfigurationPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundKeepAliveConfigurationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundKeepAliveConfigurationPacket.kt index a01f8bf..ff29237 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundKeepAliveConfigurationPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundKeepAliveConfigurationPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundKeepAliveConfigurationPacket) { buffer.writeLong(value.id) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundPingPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundPingConfigurationPacket.kt similarity index 57% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundPingPacket.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundPingConfigurationPacket.kt index 985afd0..37d18d9 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundPingPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundPingConfigurationPacket.kt @@ -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 { - override fun encode(buffer: BytesBuffer, value: ClientboundPingPacket) { +public data class ClientboundPingConfigurationPacket(val id: Int) : ClientboundConfigurationPacket { + public companion object Codec : PacketCodec { + 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()) } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundSelectKnownPacksPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundSelectKnownPacksPacket.kt index 8864527..e613ab1 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundSelectKnownPacksPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ClientboundSelectKnownPacksPacket.kt @@ -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) : MinecraftPacket { +public data class ClientboundSelectKnownPacksPacket(val knownPacks: List) : ClientboundConfigurationPacket { public companion object Codec : PacketCodec { override fun encode(buffer: BytesBuffer, value: ClientboundSelectKnownPacksPacket) { buffer.writeVarInt(value.knownPacks.size) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/KnownPacks.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/KnownPacks.kt index 38f2307..c15f0cd 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/KnownPacks.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/KnownPacks.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: KnownPacks) { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ServerboundPongPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ServerboundPongConfigurationPacket.kt similarity index 61% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ServerboundPongPacket.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ServerboundPongConfigurationPacket.kt index 3fe9db0..797c7e6 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ServerboundPongPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/ServerboundPongConfigurationPacket.kt @@ -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 { - override fun encode(buffer: BytesBuffer, value: ServerboundPongPacket) { +public data class ServerboundPongConfigurationPacket(val id: Int) : MinecraftPacket { + public companion object Codec : PacketCodec { + 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()) } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundDisconnectLoginPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundDisconnectLoginPacket.kt index 0ce7d65..24ea553 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundDisconnectLoginPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundDisconnectLoginPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {} override fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginPacket.kt new file mode 100644 index 0000000..cee9f5c --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginPacket.kt @@ -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 \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginSuccessPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginSuccessPacket.kt index 286707e..6dba6b8 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginSuccessPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundLoginSuccessPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundLoginSuccessPacket) {} override fun decode(buffer: BytesBuffer): ClientboundLoginSuccessPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundSetCompressionPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundSetCompressionPacket.kt index b2aebb4..0b06af5 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundSetCompressionPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ClientboundSetCompressionPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundSetCompressionPacket) { buffer.writeVarInt(value.threshold) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ServerboundLoginAcknowledgedPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ServerboundLoginAcknowledgedPacket.kt index 38f3576..783a2f1 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ServerboundLoginAcknowledgedPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/ServerboundLoginAcknowledgedPacket.kt @@ -13,7 +13,6 @@ import cn.rtast.libmc.common.packet.MinecraftPacket public data object ServerboundLoginAcknowledgedPacket : MinecraftPacket, PacketCodec { - override fun encode(buffer: BytesBuffer, value: ServerboundLoginAcknowledgedPacket) {} override fun decode(buffer: BytesBuffer): ServerboundLoginAcknowledgedPacket = throw UnsupportedOperationException() } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDisconnectPlayPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDisconnectPlayPacket.kt index a8f5c8a..0c3cc2b 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDisconnectPlayPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDisconnectPlayPacket.kt @@ -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 { 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()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundKeepAlivePlayPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundKeepAlivePlayPacket.kt index cb656d7..a1718bc 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundKeepAlivePlayPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundKeepAlivePlayPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundKeepAlivePlayPacket) { buffer.writeLong(value.id) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundLoginPlayPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundLoginPlayPacket.kt index 0cfaa91..e905469 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundLoginPlayPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundLoginPlayPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundLoginPlayPacket) {} override fun decode(buffer: BytesBuffer): ClientboundLoginPlayPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPingPlayPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPingPlayPacket.kt index 51f50dc..22ed854 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPingPlayPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPingPlayPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundPingPlayPacket) { buffer.writeInt(value.id) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayPacket.kt new file mode 100644 index 0000000..bd9d77f --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayPacket.kt @@ -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 \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayerChatMessagePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayerChatMessagePacket.kt index de9cd75..200e8bb 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayerChatMessagePacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundPlayerChatMessagePacket.kt @@ -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, - 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 { - 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 { - 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 { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundStartConfigurationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundStartConfigurationPacket.kt index 5f2beb8..4f181b8 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundStartConfigurationPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundStartConfigurationPacket.kt @@ -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 { override fun encode(buffer: BytesBuffer, value: ClientboundStartConfigurationPacket) {} override fun decode(buffer: BytesBuffer): ClientboundStartConfigurationPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt index b712e83..052b0fa 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt @@ -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) { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/GameMode.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/GameMode.kt index abbb196..996c0f5 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/GameMode.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/GameMode.kt @@ -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 diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt index bfc78bf..967ee44 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt @@ -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" diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/util/nbt.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/util/nbt.kt new file mode 100644 index 0000000..c74435f --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/util/nbt.kt @@ -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() \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/util/nbt.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/util/nbt.kt deleted file mode 100644 index 44a9223..0000000 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/util/nbt.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt b/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt index c0b9d85..e616dd6 100644 --- a/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt +++ b/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt @@ -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 { + println(it) + } while (true) { } } diff --git a/libmc-rconlib/build.gradle.kts b/libmc-rconlib/build.gradle.kts index 9cb79a5..fe2882f 100644 --- a/libmc-rconlib/build.gradle.kts +++ b/libmc-rconlib/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { sourceSets { commonMain.dependencies { - implementation(project(":common")) + api(project(":common")) } jvmMain.dependencies { diff --git a/libmc-snbt/build.gradle.kts b/libmc-snbt/build.gradle.kts new file mode 100644 index 0000000..fe2882f --- /dev/null +++ b/libmc-snbt/build.gradle.kts @@ -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) + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index cd3ecbb..190c36c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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(":")}") }