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

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

No files matched your search

@@ -9,9 +9,7 @@ package cn.rtast.libmc.protocol.client
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.packet.configuration.*
import cn.rtast.libmc.protocol.packet.login.ClientboundDisconnectLoginPacket
import cn.rtast.libmc.protocol.packet.login.ClientboundLoginSuccessPacket
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
import cn.rtast.libmc.protocol.packet.login.*
import cn.rtast.libmc.protocol.packet.play.*
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
@@ -21,53 +19,75 @@ internal class InternalPacketDispatcher(private val client: MinecraftClient) {
suspend fun handleIncomingPackets(packet: MinecraftPacket) {
this.dispatchEvent(packet)
when (packet) {
is ClientboundLoginPacket -> this.handleLoginPackets(packet)
is ClientboundConfigurationPacket -> this.handleConfigurationPackets(packet)
is ClientboundPlayPacket -> this.handlePlayPackets(packet)
}
}
private fun handleLoginPackets(packet: ClientboundLoginPacket) {
when (packet) {
is ClientboundDisconnectLoginPacket -> {
println("Login denied: ${packet.reason}")
client.close()
}
is ClientboundSetCompressionPacket -> client.networkChannel.setCompression(packet.threshold)
is ClientboundLoginSuccessPacket -> {
client.networkChannel.sendPacket(ServerboundLoginAcknowledgedPacket)
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
}
}
}
is ClientboundDisconnectLoginPacket -> {
println("Login denied: ${packet.reason}")
// close()
private fun handleConfigurationPackets(packet: ClientboundConfigurationPacket) {
when (packet) {
is ClientboundCookieRequestPacket -> {
// TODO
}
is ClientboundSelectKnownPacksPacket -> {
client.networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList())) // TODO empty resource packs list
}
is ClientboundPingPacket -> client.networkChannel.sendPacket(ServerboundPongPacket(packet.id))
is ClientboundKeepAliveConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundKeepAliveConfigurationPacket(packet.id))
}
is ClientboundFinishConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket)
client.stateMachine.transitionTo(ProtocolState.PLAY)
is ClientboundCustomPayloadPacket -> {
// TODO
}
is ClientboundDisconnectConfigurationPacket -> {
println("Configuration disconnected: ${packet.reason}")
// close()
}
is ClientboundLoginPlayPacket -> {
println("Successfully joined world! Entity ID: ${packet.entityId}")
ClientboundFinishConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket)
client.stateMachine.transitionTo(ProtocolState.PLAY)
}
is ClientboundKeepAlivePlayPacket -> {
client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(id = packet.id))
is ClientboundKeepAliveConfigurationPacket -> client.networkChannel.sendPacket(
ServerboundKeepAliveConfigurationPacket(packet.id)
)
is ClientboundPingConfigurationPacket -> client.networkChannel.sendPacket(
ServerboundPongConfigurationPacket(packet.id)
)
is ClientboundSelectKnownPacksPacket -> {
client.networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList())) // TODO empty resource packs list
}
}
}
private fun handlePlayPackets(packet: ClientboundPlayPacket) {
when (packet) {
is ClientboundDisconnectPlayPacket -> client.close()
is ClientboundKeepAlivePlayPacket -> client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(id = packet.id))
is ClientboundLoginPlayPacket -> println("Successfully joined world Entity ID: ${packet.entityId}")
is ClientboundPingPlayPacket -> client.networkChannel.sendPacket(ServerboundPongPlayPacket(packet.id))
is ClientboundPlayerChatMessagePacket -> {
println("Received player chat message $packet")
// TODO
}
is ClientboundStartConfigurationPacket -> {
ClientboundStartConfigurationPacket -> {
client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket)
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
}
is ClientboundDisconnectPlayPacket -> {
println("Disconnected from play session: ${packet.reason}")
// close()
}
}
}
}
@@ -10,6 +10,7 @@ import cn.rtast.libmc.common.*
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.client.ClientStateMachine
import cn.rtast.libmc.protocol.protocol.GameProtocols
import kotlin.concurrent.Volatile
internal class NetworkChannel(
private val host: String,
@@ -20,6 +21,8 @@ internal class NetworkChannel(
private var socket: Socket? = null
private var readChannel: ReadChannel? = null
private var writeChannel: WriteChannel? = null
@Volatile
private var threshold = -1
fun connect() {
@@ -37,46 +40,43 @@ internal class NetworkChannel(
val channel = requireNotNull(readChannel) { "ReadChannel not connected" }
val packetLength = channel.readVarInt()
val rawFrameBytes = channel.readBytes(packetLength)
val payloadBuf = if (threshold < 0) rawFrameBytes.wrap() else {
val frameBuf = rawFrameBytes.wrap()
val frameBuf = rawFrameBytes.wrap()
val payloadBuf = if (threshold < 0) frameBuf else {
val dataLength = frameBuf.readVarInt()
if (dataLength == 0) {
frameBuf.readBytes(frameBuf.remaining.toInt()).wrap()
} else frameBuf.readBytes(frameBuf.remaining.toInt()).zlibDecompress(dataLength).wrap()
val remainingBytes = frameBuf.readBytes(frameBuf.remaining.toInt())
if (dataLength == 0) remainingBytes.wrap() else remainingBytes.zlibDecompress(dataLength).wrap()
}
val currentState = stateMachine.currentState
val packetId = payloadBuf.readVarInt()
return GameProtocols.clientboundGameProtocols.getRegistry(currentState).decodePacket(packetId, payloadBuf)
val packet = GameProtocols.clientboundGameProtocols
.getRegistry(currentState)
.decodePacket(packetId, payloadBuf)
return packet
}
fun sendPacket(packet: MinecraftPacket) {
val channel = requireNotNull(writeChannel) { "WriteChannel not connected" }
val bodyBuffer = BytesBuffer()
GameProtocols.serverboundGameProtocols.getRegistry(stateMachine.currentState).encodePacket(bodyBuffer, packet)
val frameBuffer = BytesBuffer().apply {
if (threshold < 0) {
writeVarInt(bodyBuffer.size)
writeBuffer(bodyBuffer)
val uncompressedBodyBuf = BytesBuffer()
GameProtocols.serverboundGameProtocols
.getRegistry(stateMachine.currentState)
.encodePacket(uncompressedBodyBuf, packet)
val uncompressedData = uncompressedBodyBuf.toByteArray()
val frameBuffer = BytesBuffer()
if (threshold < 0) {
frameBuffer.writeVarInt(uncompressedData.size)
frameBuffer.writeBytes(uncompressedData)
} else {
val contentBuf = BytesBuffer()
if (uncompressedData.size < threshold) {
contentBuf.writeVarInt(0)
contentBuf.writeBytes(uncompressedData)
} else {
val uncompressedData = bodyBuffer.toByteArray()
if (uncompressedData.size < threshold) {
val contentBuf = BytesBuffer().apply {
writeVarInt(0)
writeBytes(uncompressedData)
}
writeVarInt(contentBuf.size)
writeBuffer(contentBuf)
} else {
val compressedData = uncompressedData.zlibCompress()
val contentBuf = BytesBuffer().apply {
writeVarInt(uncompressedData.size)
writeBytes(compressedData)
}
writeVarInt(contentBuf.size)
writeBuffer(contentBuf)
}
val compressedData = uncompressedData.zlibCompress()
contentBuf.writeVarInt(uncompressedData.size)
contentBuf.writeBytes(compressedData)
}
frameBuffer.writeVarInt(contentBuf.size)
frameBuffer.writeBuffer(contentBuf)
}
channel.writeFully(frameBuffer.toByteArray())
channel.flush()
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.packet.MinecraftPacket
public sealed interface ClientboundConfigurationPacket : MinecraftPacket
@@ -9,12 +9,11 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class ClientboundCookieRequestPacket(val key: Identifier) : MinecraftPacket {
public data class ClientboundCookieRequestPacket(val key: Identifier) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundCookieRequestPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundCookieRequestPacket) {
buffer.writeIdentifier(value.key)
@@ -9,12 +9,12 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class ClientboundCustomPayloadPacket(val channel: Identifier, val data: ByteArray) : MinecraftPacket {
public data class ClientboundCustomPayloadPacket(val channel: Identifier, val data: ByteArray) :
ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundCustomPayloadPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundCustomPayloadPacket) {
buffer.writeIdentifier(value.channel)
@@ -7,17 +7,16 @@
package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.protocol.util.readMinimalTextNbt
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ClientboundDisconnectConfigurationPacket(val reason: String) : MinecraftPacket {
public data class ClientboundDisconnectConfigurationPacket(val reason: NBTCompound) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket {
val reasonText = buffer.readMinimalTextNbt()
return ClientboundDisconnectConfigurationPacket(reason = reasonText)
return ClientboundDisconnectConfigurationPacket(reason = buffer.readNetworkNBTCompound())
}
}
}
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data object ClientboundFinishConfigurationPacket : MinecraftPacket,
public data object ClientboundFinishConfigurationPacket : ClientboundConfigurationPacket,
PacketCodec<ClientboundFinishConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundFinishConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundFinishConfigurationPacket {
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundKeepAliveConfigurationPacket(val id: Long) : MinecraftPacket {
public data class ClientboundKeepAliveConfigurationPacket(val id: Long) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundKeepAliveConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAliveConfigurationPacket) {
buffer.writeLong(value.id)
@@ -9,14 +9,13 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundPingPacket(val id: Int) : MinecraftPacket {
public companion object Codec : PacketCodec<ClientboundPingPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPingPacket) {
public data class ClientboundPingConfigurationPacket(val id: Int) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundPingConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPingConfigurationPacket) {
buffer.writeInt(value.id)
}
override fun decode(buffer: BytesBuffer): ClientboundPingPacket = ClientboundPingPacket(buffer.readInt())
override fun decode(buffer: BytesBuffer): ClientboundPingConfigurationPacket = ClientboundPingConfigurationPacket(buffer.readInt())
}
}
@@ -9,11 +9,10 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.common.writeVarInt
public data class ClientboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : MinecraftPacket {
public data class ClientboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : ClientboundConfigurationPacket {
public companion object Codec : PacketCodec<ClientboundSelectKnownPacksPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSelectKnownPacksPacket) {
buffer.writeVarInt(value.knownPacks.size)
@@ -7,13 +7,11 @@
package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readMcString
import cn.rtast.libmc.common.writeMcString
import kotlinx.serialization.Serializable
@Serializable
public data class KnownPacks(val namespace: String, val id: String, val version: String) {
public companion object Codec : PacketCodec<KnownPacks> {
override fun encode(buffer: BytesBuffer, value: KnownPacks) {
@@ -11,12 +11,12 @@ import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ServerboundPongPacket(val id: Int) : MinecraftPacket {
public companion object Codec : PacketCodec<ServerboundPongPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundPongPacket) {
public data class ServerboundPongConfigurationPacket(val id: Int) : MinecraftPacket {
public companion object Codec : PacketCodec<ServerboundPongConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundPongConfigurationPacket) {
buffer.writeInt(value.id)
}
override fun decode(buffer: BytesBuffer): ServerboundPongPacket = ServerboundPongPacket(buffer.readInt())
override fun decode(buffer: BytesBuffer): ServerboundPongConfigurationPacket = ServerboundPongConfigurationPacket(buffer.readInt())
}
}
@@ -9,10 +9,9 @@ package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readMcString
public data class ClientboundDisconnectLoginPacket(val reason: String) : MinecraftPacket {
public data class ClientboundDisconnectLoginPacket(val reason: String) : ClientboundLoginPacket {
public companion object Codec : PacketCodec<ClientboundDisconnectLoginPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket {
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.packet.MinecraftPacket
public sealed interface ClientboundLoginPacket : MinecraftPacket
@@ -9,12 +9,12 @@ package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readUuid
import cn.rtast.libmc.protocol.profile.GameProfile
import kotlin.uuid.Uuid
public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) : MinecraftPacket {
public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) :
ClientboundLoginPacket {
public companion object Codec : PacketCodec<ClientboundLoginSuccessPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundLoginSuccessPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundLoginSuccessPacket {
@@ -9,11 +9,10 @@ package cn.rtast.libmc.protocol.packet.login
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.common.writeVarInt
public data class ClientboundSetCompressionPacket(val threshold: Int) : MinecraftPacket {
public data class ClientboundSetCompressionPacket(val threshold: Int) : ClientboundLoginPacket {
public companion object Codec : PacketCodec<ClientboundSetCompressionPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSetCompressionPacket) {
buffer.writeVarInt(value.threshold)
@@ -13,7 +13,6 @@ import cn.rtast.libmc.common.packet.MinecraftPacket
public data object ServerboundLoginAcknowledgedPacket : MinecraftPacket,
PacketCodec<ServerboundLoginAcknowledgedPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundLoginAcknowledgedPacket) {}
override fun decode(buffer: BytesBuffer): ServerboundLoginAcknowledgedPacket = throw UnsupportedOperationException()
}
@@ -7,17 +7,16 @@
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.protocol.util.readMinimalTextNbt
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ClientboundDisconnectPlayPacket(val reason: String) : MinecraftPacket {
public data class ClientboundDisconnectPlayPacket(val reason: NBTCompound) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundDisconnectPlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectPlayPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectPlayPacket {
val reasonText = buffer.readMinimalTextNbt()
return ClientboundDisconnectPlayPacket(reason = reasonText)
return ClientboundDisconnectPlayPacket(reason = buffer.readNetworkNBTCompound())
}
}
}
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundKeepAlivePlayPacket(val id: Long) : MinecraftPacket {
public data class ClientboundKeepAlivePlayPacket(val id: Long) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundKeepAlivePlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAlivePlayPacket) {
buffer.writeLong(value.id)
@@ -7,15 +7,10 @@
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.protocol.protocol.game.BlockPos
import cn.rtast.libmc.protocol.protocol.game.GameMode
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readBlockPos
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.*
public data class ClientboundLoginPlayPacket(
val entityId: Int,
@@ -41,7 +36,7 @@ public data class ClientboundLoginPlayPacket(
val seaLevel: Int,
val isOnlineMode: Boolean,
val enforceSecureChat: Boolean,
) : MinecraftPacket {
) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundLoginPlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundLoginPlayPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundLoginPlayPacket {
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data class ClientboundPingPlayPacket(val id: Int) : MinecraftPacket {
public data class ClientboundPingPlayPacket(val id: Int) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundPingPlayPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPingPlayPacket) {
buffer.writeInt(value.id)
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.packet.MinecraftPacket
public sealed interface ClientboundPlayPacket : MinecraftPacket
@@ -8,10 +8,13 @@
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.*
import cn.rtast.libmc.common.packet.MinecraftPacket
import cn.rtast.libmc.protocol.util.writeMinimalTextNbt
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import kotlin.uuid.Uuid
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Player_Chat_Message
*/
public data class ClientboundPlayerChatMessagePacket(
val globalIndex: Int,
val sender: Uuid,
@@ -21,13 +24,13 @@ public data class ClientboundPlayerChatMessagePacket(
val timestamp: Long,
val salt: Long,
val previousMessages: List<PreviousMessageEntry>,
val unsignedContent: String?,
val unsignedContent: NBTCompound?,
val filterType: ChatFilterType,
val filterMaskBits: LongArray?,
val chatType: Int,
val senderName: String,
val targetName: String?,
) : MinecraftPacket {
val senderName: NBTCompound,
val targetName: NBTCompound?,
) : ClientboundPlayPacket {
public enum class ChatFilterType(public val id: Int) {
PASS_THROUGH(0),
FULLY_FILTERED(1),
@@ -39,32 +42,22 @@ public data class ClientboundPlayerChatMessagePacket(
}
}
public data class PreviousMessageEntry(
val messageId: Int,
val signature: ByteArray?,
) {
public data class PreviousMessageEntry(val messageId: Int, val signature: ByteArray?) {
public companion object Codec : PacketCodec<PreviousMessageEntry> {
override fun encode(buffer: BytesBuffer, value: PreviousMessageEntry) {
buffer.writeVarInt(value.messageId)
if (value.messageId == 0) {
val sig = requireNotNull(value.signature) { "signature must be present when messageId is 0" }
require(sig.size == 256)
buffer.writeBytes(sig)
}
override fun encode(buffer: BytesBuffer, value: PreviousMessageEntry) {}
override fun decode(buffer: BytesBuffer): PreviousMessageEntry {
val messageId = buffer.readVarInt()
val signature = if (messageId == 0) buffer.readBytes(256) else null
return PreviousMessageEntry(messageId, signature)
}
override fun decode(buffer: BytesBuffer): PreviousMessageEntry = throw UnsupportedOperationException() // TODO
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as PreviousMessageEntry
if (messageId != other.messageId) return false
if (!signature.contentEquals(other.signature)) return false
return true
}
@@ -76,43 +69,46 @@ public data class ClientboundPlayerChatMessagePacket(
}
public companion object Codec : PacketCodec<ClientboundPlayerChatMessagePacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPlayerChatMessagePacket) {
buffer.writeVarInt(value.globalIndex)
buffer.writeUuid(value.sender)
buffer.writeVarInt(value.index)
val hasSignature = value.messageSignature != null
buffer.writeBoolean(hasSignature)
if (hasSignature) buffer.writeBytes(requireNotNull(value.messageSignature))
override fun encode(buffer: BytesBuffer, value: ClientboundPlayerChatMessagePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundPlayerChatMessagePacket {
val globalIndex = buffer.readVarInt()
val sender = buffer.readUuid()
val index = buffer.readVarInt()
val hasSignature = buffer.readBoolean()
val messageSignature = if (hasSignature) buffer.readBytes(256) else null
buffer.writeMcString(value.message)
buffer.writeLong(value.timestamp)
buffer.writeLong(value.salt)
val message = buffer.readMcString()
val timestamp = buffer.readLong()
val salt = buffer.readLong()
require(value.previousMessages.size == 20)
buffer.writeVarInt(value.previousMessages.size)
value.previousMessages.forEach { entry -> PreviousMessageEntry.encode(buffer, entry) }
val prevMessageCount = buffer.readVarInt()
val prevMessages = List(prevMessageCount) { PreviousMessageEntry.decode(buffer) }
val hasUnsignedContent = value.unsignedContent != null
buffer.writeBoolean(hasUnsignedContent)
value.unsignedContent?.let { buffer.writeMinimalTextNbt(it) }
val hasUnsignedContent = buffer.readBoolean()
val unsignedContent = if (hasUnsignedContent) buffer.readNetworkNBTCompound() else null // ?
buffer.writeVarInt(value.filterType.id)
if (value.filterType == ChatFilterType.PARTIALLY_FILTERED) {
val mask = requireNotNull(value.filterMaskBits)
buffer.writeVarInt(mask.size)
mask.forEach { buffer.writeLong(it) }
}
val filterTypeId = buffer.readVarInt()
val filterType = ChatFilterType.fromId(filterTypeId)
buffer.writeVarInt(value.chatType)
buffer.writeMinimalTextNbt(value.senderName)
val filterMaskBits = if (filterType == ChatFilterType.PARTIALLY_FILTERED) {
val bitSetLen = buffer.readVarInt()
LongArray(bitSetLen) { buffer.readLong() }
} else null
val hasTargetName = value.targetName != null
buffer.writeBoolean(hasTargetName)
value.targetName?.let { buffer.writeMinimalTextNbt(it) }
val chatType = buffer.readVarInt()
val senderName = buffer.readNetworkNBTCompound() // ?
val hasTargetName = buffer.readBoolean()
val targetName = if (hasTargetName) buffer.readNetworkNBTCompound() else null // ?
return ClientboundPlayerChatMessagePacket(
globalIndex, sender, index,
messageSignature, message,
timestamp, salt, prevMessages,
unsignedContent, filterType,
filterMaskBits, chatType,
senderName, targetName
)
}
override fun decode(buffer: BytesBuffer): ClientboundPlayerChatMessagePacket =
throw UnsupportedOperationException() // TODO
}
override fun equals(other: Any?): Boolean {
@@ -9,9 +9,8 @@ package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.packet.MinecraftPacket
public data object ClientboundStartConfigurationPacket : MinecraftPacket,
public data object ClientboundStartConfigurationPacket : ClientboundPlayPacket,
PacketCodec<ClientboundStartConfigurationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundStartConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundStartConfigurationPacket {
@@ -25,7 +25,7 @@ internal object GameProtocols {
register(0x02, ClientboundDisconnectConfigurationPacket)
register(0x03, ClientboundFinishConfigurationPacket)
register(0x04, ClientboundKeepAliveConfigurationPacket)
register(0x05, ClientboundPingPacket)
register(0x05, ClientboundPingConfigurationPacket)
register(0x0e, ClientboundSelectKnownPacksPacket)
}
register(ProtocolState.LOGIN) {
@@ -34,6 +34,7 @@ internal object GameProtocols {
register(0x03, ClientboundSetCompressionPacket)
}
register(ProtocolState.PLAY) {
register(0x20, ClientboundDisconnectPlayPacket)
register(0x2c, ClientboundKeepAlivePlayPacket)
register(0x31, ClientboundLoginPlayPacket)
register(0x3d, ClientboundPingPlayPacket)
@@ -49,7 +50,7 @@ internal object GameProtocols {
register(ProtocolState.CONFIGURATION) {
register(0x03, ServerboundAckFinishConfigurationPacket)
register(0x04, ServerboundKeepAliveConfigurationPacket)
register(0x05, ServerboundPongPacket)
register(0x05, ServerboundPongConfigurationPacket)
register(0x07, ServerboundSelectKnownPacksPacket)
}
register(ProtocolState.LOGIN) {
@@ -17,7 +17,7 @@ public enum class GameMode(public val id: Byte) {
/**
* reserved
*/
Unknown(-99);
Unknown(-128);
public companion object {
public fun fromID(id: Byte): GameMode = entries.firstOrNull { it.id == id } ?: Unknown
@@ -7,19 +7,21 @@
package cn.rtast.libmc.protocol.protocol.game
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readMcString
import cn.rtast.libmc.common.writeMcString
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Identifier
*/
@JvmInline
public value class Identifier(public val full: String) {
public val namespace: String get() = if (full.contains(':')) full.substringBefore(':') else "minecraft"
public val path: String get() = if (full.contains(':')) full.substringAfter(':') else full
@Serializable
public value class Identifier internal constructor(public val raw: String) {
public val namespace: String get() = if (raw.contains(':')) raw.substringBefore(':') else "minecraft"
public val path: String get() = if (raw.contains(':')) raw.substringAfter(':') else raw
override fun toString(): String = "$namespace:$path"
@@ -0,0 +1,20 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.protocol.util
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.nbt.readNetworkCompound
import cn.rtast.libmc.nbt.readRootCompound
import cn.rtast.libmc.nbt.toNBTInput
internal fun BytesBuffer.readNBTCompound(): NBTCompound =
this.toNBTInput().readRootCompound()
internal fun BytesBuffer.readNetworkNBTCompound(): NBTCompound =
this.toNBTInput().readNetworkCompound()
@@ -1,43 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.libmc.protocol.util
import cn.rtast.libmc.common.BytesBuffer
/**
* tmp
*/
internal fun BytesBuffer.writeMinimalTextNbt(text: String) {
writeByte(0x0A)
writeByte(0x08)
val keyBytes = "text".encodeToByteArray()
writeShort(keyBytes.size.toShort())
writeBytes(keyBytes)
val valBytes = text.encodeToByteArray()
require(valBytes.size <= 32767)
writeShort(valBytes.size.toShort())
writeBytes(valBytes)
writeByte(0x00)
}
internal fun BytesBuffer.readMinimalTextNbt(): String {
val rootTagType = readByte().toInt()
if (rootTagType != 0x0A) return ""
var resultText = ""
while (true) {
val tagType = readByte().toInt()
if (tagType == 0x00) break
val keyLength = readShort().toInt()
val key = readBytes(keyLength).decodeToString()
if (tagType == 0x08 && key == "text") {
val valLength = readShort().toInt()
resultText = readBytes(valLength).decodeToString()
} else break
}
return resultText
}
@@ -8,6 +8,7 @@
package test
import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.packet.play.ClientboundLoginPlayPacket
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
@@ -19,6 +20,9 @@ class TestClient {
val cli = createMinecraftClient("127.0.0.1", 25565, "123")
cli.launch { cli.connect() }
cli.on<ClientboundLoginPlayPacket> {
println(it)
}
while (true) {
}
}