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