Fix set compresion, added nbt and snbt(wip) lib
This commit is contained in:
56 files changed
+1064
-544
No files matched your search
@@ -19,6 +19,7 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
api(project(":common"))
|
||||
api(project(":nbt"))
|
||||
api(libs.kotlinx.serialization.core)
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.coroutines)
|
||||
|
||||
+50
-30
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-30
@@ -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()
|
||||
|
||||
+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.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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+5
-6
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
+1
-2
@@ -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)
|
||||
|
||||
+4
-5
@@ -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())
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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)
|
||||
|
||||
+1
-3
@@ -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) {
|
||||
|
||||
+4
-4
@@ -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())
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
+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.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 {
|
||||
|
||||
+1
-2
@@ -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)
|
||||
|
||||
-1
@@ -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()
|
||||
}
|
||||
+5
-6
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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)
|
||||
|
||||
+3
-8
@@ -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 {
|
||||
|
||||
+1
-2
@@ -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)
|
||||
|
||||
+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
|
||||
|
||||
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 {
|
||||
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
+3
-2
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+6
-4
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user