Added some packets

This commit is contained in:
2026-09-05 18:19:58 +08:00
parent a2eebe3d4e
commit 94cca61df1
22 files changed
+326 -45

No files matched your search

@@ -9,7 +9,6 @@ package cn.rtast.libmc.common
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
@Suppress("CLASSNAME")
public expect class BytesBuffer { public expect class BytesBuffer {
public constructor() public constructor()
public constructor(bytes: ByteArray) public constructor(bytes: ByteArray)
@@ -18,6 +17,8 @@ public expect class BytesBuffer {
public fun writeShort(value: Short, endian: ByteOrder = ByteOrder.BIG_ENDIAN) public fun writeShort(value: Short, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeInt(value: Int, endian: ByteOrder = ByteOrder.BIG_ENDIAN) public fun writeInt(value: Int, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeLong(value: Long, endian: ByteOrder = ByteOrder.BIG_ENDIAN) public fun writeLong(value: Long, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeDouble(value: Double, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeFloat(value: Float, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeBytes(bytes: ByteArray) public fun writeBytes(bytes: ByteArray)
public fun writeBoolean(value: Boolean) public fun writeBoolean(value: Boolean)
@@ -25,6 +26,8 @@ public expect class BytesBuffer {
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
public fun readDouble(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Double
public fun readFloat(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Float
public fun readBytes(length: Int): ByteArray public fun readBytes(length: Int): ByteArray
public fun readBoolean(): Boolean public fun readBoolean(): Boolean
public fun readRemainingBytes(): ByteArray public fun readRemainingBytes(): ByteArray
@@ -7,7 +7,6 @@
package cn.rtast.libmc.common package cn.rtast.libmc.common
@Suppress("CLASSNAME")
public expect class ReadChannel { public expect class ReadChannel {
public fun readByte(): Byte public fun readByte(): Byte
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
@@ -17,7 +16,6 @@ public expect class ReadChannel {
public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size) public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
} }
@Suppress("CLASSNAME")
public expect class WriteChannel { public expect class WriteChannel {
public fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size) public fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
public fun flush() public fun flush()
@@ -7,15 +7,13 @@
package cn.rtast.libmc.common package cn.rtast.libmc.common
@Suppress("CLASSNAME")
public expect class Socket public constructor(host: String, port: Int, context: LibMCContext) { public expect class Socket public constructor(host: String, port: Int, context: LibMCContext) {
public fun openReadChannel(): ReadChannel public fun openReadChannel(): ReadChannel
public fun openWriteChannel(): WriteChannel public fun openWriteChannel(): WriteChannel
public fun close() public fun close()
} }
@Suppress("CLASSNAME") public expect class UdpSocket public constructor(host: String, port: Int, context: LibMCContext) {
public expect class _UdpSocket public constructor(host: String, port: Int, context: LibMCContext) {
public fun sendAndReceive(data: ByteArray): ByteArray public fun sendAndReceive(data: ByteArray): ByteArray
public fun close() public fun close()
} }
@@ -8,7 +8,6 @@ package cn.rtast.libmc.common
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@Suppress("CLASSNAME")
public actual class BytesBuffer { public actual class BytesBuffer {
private val outStream = ByteArrayOutputStream() private val outStream = ByteArrayOutputStream()
private var readBuffer: ByteArray? = null private var readBuffer: ByteArray? = null
@@ -26,6 +25,7 @@ public actual class BytesBuffer {
} }
public actual fun writeShort(value: Short, endian: ByteOrder) { public actual fun writeShort(value: Short, endian: ByteOrder) {
readBuffer = null
val v = value.toInt() val v = value.toInt()
if (endian == ByteOrder.BIG_ENDIAN) { if (endian == ByteOrder.BIG_ENDIAN) {
outStream.write(v shr 8) outStream.write(v shr 8)
@@ -37,6 +37,7 @@ public actual class BytesBuffer {
} }
public actual fun writeInt(value: Int, endian: ByteOrder) { public actual fun writeInt(value: Int, endian: ByteOrder) {
readBuffer = null
if (endian == ByteOrder.BIG_ENDIAN) { if (endian == ByteOrder.BIG_ENDIAN) {
outStream.write(value shr 24) outStream.write(value shr 24)
outStream.write(value shr 16) outStream.write(value shr 16)
@@ -51,6 +52,7 @@ public actual class BytesBuffer {
} }
public actual fun writeLong(value: Long, endian: ByteOrder) { public actual fun writeLong(value: Long, endian: ByteOrder) {
readBuffer = null
if (endian == ByteOrder.BIG_ENDIAN) { if (endian == ByteOrder.BIG_ENDIAN) {
for (i in 56 downTo 0 step 8) outStream.write((value shr i).toInt()) for (i in 56 downTo 0 step 8) outStream.write((value shr i).toInt())
} else { } else {
@@ -58,6 +60,14 @@ public actual class BytesBuffer {
} }
} }
public actual fun writeDouble(value: Double, endian: ByteOrder) {
writeLong(value.toRawBits(), endian)
}
public actual fun writeFloat(value: Float, endian: ByteOrder) {
writeInt(value.toRawBits(), endian)
}
public actual fun writeBytes(bytes: ByteArray) { public actual fun writeBytes(bytes: ByteArray) {
readBuffer = null readBuffer = null
outStream.write(bytes) outStream.write(bytes)
@@ -83,6 +93,15 @@ public actual class BytesBuffer {
public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian) public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian) public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian) public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
public actual fun readDouble(endian: ByteOrder): Double {
return Double.fromBits(readLong(endian))
}
public actual fun readFloat(endian: ByteOrder): Float {
return Float.fromBits(readInt(endian))
}
public actual fun readBytes(length: Int): ByteArray { public actual fun readBytes(length: Int): ByteArray {
val array = ensureReadArray() val array = ensureReadArray()
if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow") if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow")
@@ -96,7 +115,9 @@ public actual class BytesBuffer {
public actual fun readRemainingBytes(): ByteArray { public actual fun readRemainingBytes(): ByteArray {
val array = ensureReadArray() val array = ensureReadArray()
if (readOffset >= array.size) return byteArrayOf() if (readOffset >= array.size) return byteArrayOf()
return array.copyOfRange(readOffset, array.size) val result = array.copyOfRange(readOffset, array.size)
readOffset = array.size
return result
} }
public actual fun toByteArray(): ByteArray = outStream.toByteArray() public actual fun toByteArray(): ByteArray = outStream.toByteArray()
@@ -11,7 +11,6 @@ import java.io.EOFException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@Suppress("CLASSNAME")
public actual class ReadChannel(private val _inputStream: InputStream) { public actual class ReadChannel(private val _inputStream: InputStream) {
public actual fun readByte(): Byte = _inputStream.read().toByte() public actual fun readByte(): Byte = _inputStream.read().toByte()
@@ -32,7 +31,6 @@ public actual class ReadChannel(private val _inputStream: InputStream) {
public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian) public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
} }
@Suppress("CLASSNAME")
public actual class WriteChannel { public actual class WriteChannel {
private val _outputStream: OutputStream private val _outputStream: OutputStream
@@ -11,7 +11,6 @@ import java.net.DatagramSocket
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Socket as JvmSocket import java.net.Socket as JvmSocket
@Suppress("CLASSNAME")
public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) { public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val socket = JvmSocket(host, port) private val socket = JvmSocket(host, port)
@@ -20,8 +19,7 @@ public actual class Socket public actual constructor(host: String, port: Int, co
public actual fun close(): Unit = socket.close() public actual fun close(): Unit = socket.close()
} }
@Suppress("CLASSNAME") public actual class UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) {
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val socket = DatagramSocket().apply { private val socket = DatagramSocket().apply {
soTimeout = 3000 soTimeout = 3000
connect(InetSocketAddress(host, port)) connect(InetSocketAddress(host, port))
@@ -6,12 +6,10 @@
package cn.rtast.libmc.common package cn.rtast.libmc.common
import io.ktor.utils.io.bits.*
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
import kotlinx.io.*
import kotlinx.io.Buffer import kotlinx.io.Buffer
import kotlinx.io.readByteArray
@Suppress("CLASSNAME")
public actual class BytesBuffer { public actual class BytesBuffer {
private val _delegateBuf: Buffer private val _delegateBuf: Buffer
@@ -26,37 +24,43 @@ public actual class BytesBuffer {
public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value) public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value)
public actual fun writeShort(value: Short, endian: ByteOrder) { public actual fun writeShort(value: Short, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeShort(value) if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeShort(value)
else _delegateBuf.writeShort(value.reverseByteOrder()) else _delegateBuf.writeShortLe(value)
} }
public actual fun writeInt(value: Int, endian: ByteOrder) { public actual fun writeInt(value: Int, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeInt(value) if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeInt(value)
else _delegateBuf.writeInt(value.reverseByteOrder()) else _delegateBuf.writeIntLe(value)
} }
public actual fun writeLong(value: Long, endian: ByteOrder) { public actual fun writeLong(value: Long, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeLong(value) if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeLong(value)
else _delegateBuf.writeLong(value.reverseByteOrder()) else _delegateBuf.writeLongLe(value)
} }
public actual fun writeDouble(value: Double, endian: ByteOrder): Unit =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeDouble(value) else _delegateBuf.writeDoubleLe(value)
public actual fun writeFloat(value: Float, endian: ByteOrder): Unit =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeFloat(value) else _delegateBuf.writeFloatLe(value)
public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes) public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes)
public actual fun writeBoolean(value: Boolean): Unit = _delegateBuf.writeByte(if (value) 0x01 else 0x00) public actual fun writeBoolean(value: Boolean): Unit = _delegateBuf.writeByte(if (value) 0x01 else 0x00)
public actual fun readByte(): Byte = _delegateBuf.readByte() public actual fun readByte(): Byte = _delegateBuf.readByte()
public actual fun readShort(endian: ByteOrder): Short { public actual fun readShort(endian: ByteOrder): Short =
val v = _delegateBuf.readShort() if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readShort() else _delegateBuf.readShortLe()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readInt(endian: ByteOrder): Int { public actual fun readInt(endian: ByteOrder): Int =
val v = _delegateBuf.readInt() if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readInt() else _delegateBuf.readIntLe()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readLong(endian: ByteOrder): Long { public actual fun readLong(endian: ByteOrder): Long =
val v = _delegateBuf.readLong() if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readLong() else _delegateBuf.readLongLe()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
} public actual fun readDouble(endian: ByteOrder): Double =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readDouble() else _delegateBuf.readDoubleLe()
public actual fun readFloat(endian: ByteOrder): Float =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readFloat() else _delegateBuf.readFloatLe()
public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length) public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
public actual fun readBoolean(): Boolean = _delegateBuf.readByte() != 0x00.toByte() public actual fun readBoolean(): Boolean = _delegateBuf.readByte() != 0x00.toByte()
@@ -10,7 +10,6 @@ import io.ktor.utils.io.*
import io.ktor.utils.io.bits.reverseByteOrder import io.ktor.utils.io.bits.reverseByteOrder
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@Suppress("CLASSNAME")
public actual class ReadChannel(private val _readChannel: ByteReadChannel) { public actual class ReadChannel(private val _readChannel: ByteReadChannel) {
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() } public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
@@ -34,7 +33,6 @@ public actual class ReadChannel(private val _readChannel: ByteReadChannel) {
} }
} }
@Suppress("CLASSNAME")
public actual class WriteChannel { public actual class WriteChannel {
private val _writeChannel: ByteWriteChannel private val _writeChannel: ByteWriteChannel
@@ -11,7 +11,6 @@ import io.ktor.utils.io.core.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.io.readByteArray import kotlinx.io.readByteArray
@Suppress("CLASSNAME")
public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) { public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val ctx = context private val ctx = context
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) } private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
@@ -26,8 +25,7 @@ public actual class Socket public actual constructor(host: String, port: Int, co
} }
} }
@Suppress("CLASSNAME") public actual class UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) {
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val ctx = context private val ctx = context
// use bind to create an unconnected socket // use bind to create an unconnected socket
@@ -9,10 +9,10 @@ package cn.rtast.libmc.mcping.bedrock
import cn.rtast.libmc.common.PacketCodec import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common._UdpSocket import cn.rtast.libmc.common.UdpSocket
internal fun <T : MinecraftBedrockPacket> _UdpSocket.sendPacket(packet: T, codec: PacketCodec<T>): ByteArray { internal fun <T : MinecraftBedrockPacket> UdpSocket.sendPacket(packet: T, codec: PacketCodec<T>): ByteArray {
val buf = BytesBuffer() val buf = BytesBuffer()
codec.encode(buf, packet) codec.encode(buf, packet)
return sendAndReceive(buf.toByteArray()) return sendAndReceive(buf.toByteArray())
@@ -8,13 +8,13 @@
package cn.rtast.libmc.mcping.bedrock package cn.rtast.libmc.mcping.bedrock
import cn.rtast.libmc.common.LibMCContext import cn.rtast.libmc.common.LibMCContext
import cn.rtast.libmc.common._UdpSocket import cn.rtast.libmc.common.UdpSocket
import cn.rtast.libmc.common.wrap import cn.rtast.libmc.common.wrap
import cn.rtast.libmc.mcping.PingResponse import cn.rtast.libmc.mcping.PingResponse
import kotlin.time.Clock import kotlin.time.Clock
internal fun pingBedrockServer(host: String, port: Int, context: LibMCContext): PingResponse { internal fun pingBedrockServer(host: String, port: Int, context: LibMCContext): PingResponse {
val socket = _UdpSocket(host, port, context) val socket = UdpSocket(host, port, context)
return try { return try {
val sendTime = Clock.System.now().toEpochMilliseconds() val sendTime = Clock.System.now().toEpochMilliseconds()
val requestPacket = BedrockRequestPacket(sendTime) val requestPacket = BedrockRequestPacket(sendTime)
@@ -88,6 +88,8 @@ internal class InternalPacketDispatcher(private val client: MinecraftClient) {
client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket) client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket)
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION) client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
} }
is ClientboundSystemChatMessagePacket -> {}
} }
} }
} }
@@ -0,0 +1,18 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
public data object ClientboundDelimiterPacket : ClientboundPlayPacket, PacketCodec<ClientboundDelimiterPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDelimiterPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDelimiterPacket {
return ClientboundDelimiterPacket
}
}
@@ -0,0 +1,28 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.protocol.types.Animations
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Entity_Animation
*/
public data class ClientboundEntityAnimationPacket(val entityId: Int, val animation: Animations) :
ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundEntityAnimationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundEntityAnimationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundEntityAnimationPacket {
val entityId = buffer.readVarInt()
val animation = Animations.fromID(buffer.readByte())
return ClientboundEntityAnimationPacket(entityId, animation)
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readUuid
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.protocol.types.Angle
import cn.rtast.libmc.protocol.types.LpVec3
import cn.rtast.libmc.protocol.types.readAngle
import cn.rtast.libmc.protocol.types.readLpVec3
import kotlin.uuid.Uuid
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Spawn_Entity
*/
public data class ClientboundSpawnEntityPacket(
val entityId: Int,
val entityUuid: Uuid,
val type: Int,
val x: Double,
val y: Double,
val z: Double,
val velocity: LpVec3,
val pitch: Angle,
val yaw: Angle,
val headYaw: Angle,
val data: Int,
) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundSpawnEntityPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSpawnEntityPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSpawnEntityPacket {
val entityId = buffer.readVarInt()
val entityUuid = buffer.readUuid()
val type = buffer.readVarInt()
val x = buffer.readDouble()
val y = buffer.readDouble()
val z = buffer.readDouble()
val velocity = buffer.readLpVec3()
val pitch = buffer.readAngle()
val yaw = buffer.readAngle()
val headYaw = buffer.readAngle()
val data = buffer.readVarInt()
return ClientboundSpawnEntityPacket(
entityId, entityUuid,
type, x, y, z, velocity,
pitch, yaw, headYaw, data
)
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
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 ClientboundSystemChatMessagePacket(val content: NBTCompound, val overlay: Boolean) :
ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundSystemChatMessagePacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSystemChatMessagePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSystemChatMessagePacket {
val content = buffer.readNetworkNBTCompound()
val overlay = buffer.readBoolean()
return ClientboundSystemChatMessagePacket(content, overlay)
}
}
}
@@ -8,11 +8,7 @@ package cn.rtast.libmc.protocol.protocol
import cn.rtast.libmc.protocol.packet.configuration.* import cn.rtast.libmc.protocol.packet.configuration.*
import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket
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.ClientboundSetCompressionPacket
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginStartPacket
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
import cn.rtast.libmc.protocol.protocol.state.ProtocolStateRegistry import cn.rtast.libmc.protocol.protocol.state.ProtocolStateRegistry
@@ -40,6 +36,7 @@ internal object GameProtocols {
register(0x3d, ClientboundPingPlayPacket) register(0x3d, ClientboundPingPlayPacket)
register(0x41, ClientboundPlayerChatMessagePacket) register(0x41, ClientboundPlayerChatMessagePacket)
register(0x76, ClientboundStartConfigurationPacket) register(0x76, ClientboundStartConfigurationPacket)
register(0x79, ClientboundSystemChatMessagePacket)
} }
} }
@@ -58,6 +55,9 @@ internal object GameProtocols {
register(0x03, ServerboundLoginAcknowledgedPacket) register(0x03, ServerboundLoginAcknowledgedPacket)
} }
register(ProtocolState.PLAY) { register(ProtocolState.PLAY) {
register(0x00, ClientboundDelimiterPacket)
register(0x01, ClientboundSpawnEntityPacket)
register(0x02, ClientboundEntityAnimationPacket)
register(0x09, ServerboundChatMessagePacket) register(0x09, ServerboundChatMessagePacket)
register(0x2d, ServerboundPongPlayPacket) register(0x2d, ServerboundPongPlayPacket)
register(0x10, ServerboundConfigurationAcknowledgedPacket) register(0x10, ServerboundConfigurationAcknowledgedPacket)
@@ -27,6 +27,7 @@ public value class Identifier internal constructor(public val raw: String) {
public companion object Codec : PacketCodec<Identifier> { public companion object Codec : PacketCodec<Identifier> {
public fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path") public fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path")
public fun of(full: String): Identifier = Identifier(full)
override fun encode(buffer: BytesBuffer, value: Identifier) { override fun encode(buffer: BytesBuffer, value: Identifier) {
buffer.writeMcString(value.toString()) buffer.writeMcString(value.toString())
@@ -0,0 +1,31 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.types
import cn.rtast.libmc.common.BytesBuffer
import kotlin.jvm.JvmInline
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Type:Angle
*/
@JvmInline
public value class Angle(public val raw: Byte) {
/**
* convert the Angle(byte) to 360-degree float angles
*/
public fun toDegrees(): Float = (raw.toInt() and 0xFF) * 360.0f / 256.0f
/**
* encode 360-degress float angles into Angle(byte)
*/
public fun toAngleByte(): Byte = ((raw % 360.0f) * 256.0f / 360.0f).toInt().toByte()
}
internal fun BytesBuffer.readAngle(): Angle = Angle(this.readByte())
internal fun BytesBuffer.writeAngle(value: Angle) = this.writeByte(value.raw)
@@ -0,0 +1,24 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.types
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Entity_Animation
*/
public enum class Animations(public val animationId: Byte) {
SwingMainArm(0),
LeaveBed(2),
SwingOffhand(3),
CriticalEffect(4),
MagicCriticalEffect(5);
public companion object {
public fun fromID(id: Byte): Animations =
entries.firstOrNull { it.animationId == id } ?: throw IllegalArgumentException("Unknown animation ID")
}
}
@@ -0,0 +1,75 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.types
import cn.rtast.libmc.common.ByteOrder
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.common.writeVarInt
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
// ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3
public data class LpVec3(
val x: Double,
val y: Double,
val z: Double,
) {
@Suppress("UNUSED")
public companion object {
public val ZERO: LpVec3 = LpVec3(0.0, 0.0, 0.0)
private const val MAX_QUANTIZED_VALUE = 32766.0
private const val CONTINUATION_FLAG = 0x04L
private const val SCALE_BITS = 0x03L
public fun pack(value: Long): Long =
((value * 0.5 + 0.5) * MAX_QUANTIZED_VALUE).toLong()
public fun unpack(value: Long): Double =
min((value and 32767L).toDouble(), MAX_QUANTIZED_VALUE) * 2.0 / MAX_QUANTIZED_VALUE - 1.0
}
}
// ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3
internal fun BytesBuffer.readLpVec3(): LpVec3 {
val byte1 = readByte().toInt() and 0xFF
if (byte1 == 0) return LpVec3.ZERO
val byte2 = readByte().toInt() and 0xFF
val bytes3To6 = readInt(ByteOrder.BIG_ENDIAN).toLong() and 0xFFFFFFFFL
val packed = (bytes3To6 shl 16) or (byte2.toLong() shl 8) or byte1.toLong()
var scaleFactor = byte1.toLong() and 0x03L
if ((byte1.toLong() and 0x04L) != 0L) scaleFactor = scaleFactor or (readVarInt().toLong() shl 2)
val scale = scaleFactor.toDouble()
val x = LpVec3.unpack(packed shr 3) * scale
val y = LpVec3.unpack(packed shr 18) * scale
val z = LpVec3.unpack(packed shr 33) * scale
return LpVec3(x, y, z)
}
// ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3
internal fun BytesBuffer.writeLpVec3(vec3: LpVec3) {
val maxCoordinate = max(abs(vec3.x), max(abs(vec3.y), abs(vec3.z)))
if (maxCoordinate.isNaN() || maxCoordinate < 1.0 / 32766.0) {
writeByte(0x00)
return
}
val scaleFactor = ceil(maxCoordinate).toLong()
val needContinuation = (scaleFactor and 0x03L) != scaleFactor
val packedScale = if (needContinuation) ((scaleFactor and 0x03L) or 0x04L) else scaleFactor
val packedX = LpVec3.pack((vec3.x / scaleFactor.toDouble()).toLong()) shl 3
val packedY = LpVec3.pack((vec3.y / scaleFactor.toDouble()).toLong()) shl 18
val packedZ = LpVec3.pack((vec3.z / scaleFactor.toDouble()).toLong()) shl 33
val packed = packedZ or packedY or packedX or packedScale
writeByte(packed.toByte())
writeByte((packed shr 8).toByte())
writeInt((packed shr 16).toInt(), ByteOrder.BIG_ENDIAN)
if (needContinuation) writeVarInt((scaleFactor shr 2).toInt())
}
@@ -10,6 +10,7 @@ package test
import cn.rtast.libmc.protocol.client.createMinecraftClient import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.packet.play.ClientboundDisconnectPlayPacket import cn.rtast.libmc.protocol.packet.play.ClientboundDisconnectPlayPacket
import cn.rtast.libmc.protocol.packet.play.ClientboundLoginPlayPacket import cn.rtast.libmc.protocol.packet.play.ClientboundLoginPlayPacket
import cn.rtast.libmc.protocol.packet.play.ClientboundSystemChatMessagePacket
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
@@ -27,6 +28,9 @@ class TestClient {
cli.on<ClientboundDisconnectPlayPacket> { cli.on<ClientboundDisconnectPlayPacket> {
println(it) println(it)
} }
cli.on<ClientboundSystemChatMessagePacket> {
println(it)
}
while (true) { while (true) {
} }
} }