From c0f9679090fc7f856cc88e8ce788ee2175c59648 Mon Sep 17 00:00:00 2001 From: RTAkland Date: Mon, 7 Sep 2026 10:07:10 +0800 Subject: [PATCH] Add all packets handler --- README.md | 2 + build.gradle.kts | 2 +- docs/implement-encryption.md | 32 +++----- gradle.properties | 4 +- gradle/libs.versions.toml | 3 + .../cn/rtast/libmc/packet/PacketRegistry.kt | 4 +- .../kotlin/cn/rtast/libmc/stream/buffer.kt | 5 -- .../kotlin/cn/rtast/libmc/stream/channels.kt | 20 ++--- .../cn/rtast/libmc/stream/socket.native.kt | 2 +- .../libmc/mcping/bedrock/ping_bedrock.kt | 2 +- .../cn/rtast/libmc/mcping/java/buffer.kt | 31 ++++++++ .../cn/rtast/libmc/mcping/java/ping_java.kt | 7 +- .../kotlin/cn/rtast/libmc/mcping/mcping.kt | 2 +- .../src/jvmTest/kotlin/test/TestJvmClient.kt | 2 +- libmc-protocol/build.gradle.kts | 15 +++- .../libmc/protocol/client/MinecraftClient.kt | 13 ++-- .../event/InternalPacketDispatcher.kt | 7 +- .../protocol/event/PacketEventDispatcher.kt | 74 ++++++++++++++++--- .../libmc/protocol/network/CipherChannel.kt | 6 ++ .../libmc/protocol/network/NetworkChannel.kt | 17 ++++- .../protocol/protocol/PacketDirection.kt | 2 +- .../src/commonTest/kotlin/test/TestClient.kt | 3 +- .../kotlin/test/TestClientTestInJvm.kt | 48 ++++++------ .../cn/rtast/libmc/mcping/rconlib/rconlib.kt | 2 +- settings.gradle.kts | 2 +- 25 files changed, 202 insertions(+), 105 deletions(-) create mode 100644 libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/buffer.kt diff --git a/README.md b/README.md index 76f4eaa..bc3a77f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ A lightweight minecraft client-side protocol library and related library, includ # libmc-protocol +[Use libmc-protocol](https://repo.rtast.cn/packages/-/cn.rtast.libmc:protocol) + --- # Minecraft Protocol Library Status & Roadmap diff --git a/build.gradle.kts b/build.gradle.kts index b0f38c3..047f24e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ allprojects { group = "cn.rtast.libmc" val libVersion = getProperty("libVersion") version = when (name) { - "protocol" -> "${getProperty("protocolVersion")}-$libVersion" + "protocol" -> getProperty("protocolVersion") else -> libVersion } diff --git a/docs/implement-encryption.md b/docs/implement-encryption.md index efd105c..7f203e2 100644 --- a/docs/implement-encryption.md +++ b/docs/implement-encryption.md @@ -4,27 +4,13 @@ * **Auth Join Request** (`AuthenticationProvider`): Sends the HTTP POST request to Mojang's session server (`https://sessionserver.mojang.com/session/minecraft/join`). +[Use libmc-protocol-encrypt](https://repo.rtast.cn/packages/-/cn.rtast.libmc:protocol-encrypt) + ```kotlin -val client = createMinecraftClient( - host = "127.0.0.1", - username = "Player", - accessToken = accessToken -) { - // 1. AES-128-CFB8 Cipher Implementation - cipherFactory = { sharedKey -> JvmAesCipher(sharedKey) } - - // 2. SHA-1 Hasher Implementation - sha1Hasher = Sha1Hasher { serverId, secretKey, publicKey -> - /* your SHA-1 implementation */ - } - - // 3. RSA-1024 Encryptor Implementation - rsaEncryptor = RSA1024Encryptor { publicKey, data -> - /* your RSA encryption implementation */ - } - - // 4. Mojang Auth HTTP Join Provider - authProvider = AuthenticationProvider { accessToken, uuid, serverIdHash -> - /* send HTTP POST to session server using your preferred HTTP client (Ktor, OkHttp, etc.) */ - } -} \ No newline at end of file +fun main() { + val client = createMinecraftClient( + // other parameter + crypto = DefaultProtocolContext + ) +} +``` \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fcd0237..39800ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,5 @@ kotlin.native.ignoreDisabledTargets=true kotlin.native.enableKlibsCrossCompilation=true kotlin.daemon.jvmargs=-Xmx2048M org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 -libVersion=0.1.0 -protocolVersion=26.2 \ No newline at end of file +libVersion=0.1.1 +protocolVersion=26.2-0.1.1 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7e02db..ec768e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ coroutines-test = "1.11.0" kotlinx-coroutines = "1.11.0" ktor-core = "3.5.2" cryptography-core = "0.6.0" +kotlinx-atomicfu = "0.33.0" #kotlinx-serialization = "1.11.0" [libraries] @@ -20,10 +21,12 @@ ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "kto ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor-core" } ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor-core" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor-core" } +kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinx-atomicfu" } #kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } #kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } maven-publish = { id = "maven-publish" } +kotlinx-atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "kotlinx-atomicfu" } #kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/packet/PacketRegistry.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/packet/PacketRegistry.kt index bd67db7..457ec3c 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/packet/PacketRegistry.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/packet/PacketRegistry.kt @@ -15,9 +15,7 @@ public class PacketRegistry { private val idToCodec = mutableMapOf>() private val classToInfo = mutableMapOf, RegisteredPacket<*>>() - private data class RegisteredPacket

( - val id: Int, val codec: PacketCodec

, - ) + private data class RegisteredPacket

(val id: Int, val codec: PacketCodec

) public fun register(id: Int, kClass: KClass, codec: PacketCodec) { idToCodec[id] = codec diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/buffer.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/buffer.kt index 5a9f604..b7ee236 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/buffer.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/buffer.kt @@ -40,8 +40,3 @@ public expect class BytesBuffer { @Suppress("NOTHING_TO_INLINE") public inline fun ByteArray.wrap(): BytesBuffer = BytesBuffer(this) - -public suspend fun ReadChannel.readPacketFrame(): BytesBuffer { - val length = this.readVarInt() - return this.readBytes(length).wrap() -} \ No newline at end of file diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/channels.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/channels.kt index 1e0936f..ff5039c 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/channels.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/stream/channels.kt @@ -7,6 +7,9 @@ package cn.rtast.libmc.stream +/** + * Platform specified raw byte read channel + */ public expect open class ReadChannel() { public open suspend fun readByte(): Byte public open suspend fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short @@ -16,21 +19,10 @@ public expect open class ReadChannel() { public open suspend fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size) } +/** + * Platform specified raw byte write channel + */ public expect open class WriteChannel() { public open suspend fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size) public open suspend fun flush() -} - -public suspend fun ReadChannel.readVarInt(): Int { - var numRead = 0 - var result = 0 - var read: Byte - do { - read = this.readByte() - val value = (read.toInt() and 0x7F) - result = result or (value shl (7 * numRead)) - numRead++ - if (numRead > 5) throw IllegalArgumentException("VarInt is too big") - } while ((read.toInt() and 0x80) != 0) - return result } \ No newline at end of file diff --git a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/stream/socket.native.kt b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/stream/socket.native.kt index 36526de..cd4b9d4 100644 --- a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/stream/socket.native.kt +++ b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/stream/socket.native.kt @@ -6,7 +6,7 @@ package cn.rtast.libmc.stream -import cn.rtast.libmc.common.LibMCContext +import cn.rtast.libmc.LibMCContext import io.ktor.network.sockets.* import io.ktor.utils.io.core.* import kotlinx.coroutines.runBlocking diff --git a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt index 6481854..d0dc847 100644 --- a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt +++ b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt @@ -7,7 +7,7 @@ package cn.rtast.libmc.mcping.bedrock -import cn.rtast.libmc.common.LibMCContext +import cn.rtast.libmc.LibMCContext import cn.rtast.libmc.stream.UdpSocket import cn.rtast.libmc.stream.wrap import cn.rtast.libmc.mcping.PingResponse diff --git a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/buffer.kt b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/buffer.kt new file mode 100644 index 0000000..48d847a --- /dev/null +++ b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/buffer.kt @@ -0,0 +1,31 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/7 + */ + + +package cn.rtast.libmc.mcping.java + +import cn.rtast.libmc.stream.BytesBuffer +import cn.rtast.libmc.stream.ReadChannel +import cn.rtast.libmc.stream.wrap + +private suspend fun ReadChannel.readVarInt(): Int { + var numRead = 0 + var result = 0 + var read: Byte + do { + read = this.readByte() + val value = (read.toInt() and 0x7F) + result = result or (value shl (7 * numRead)) + numRead++ + if (numRead > 5) throw IllegalArgumentException("VarInt is too big") + } while ((read.toInt() and 0x80) != 0) + return result +} + +public suspend fun ReadChannel.readPacketFrame(): BytesBuffer { + val length = this.readVarInt() + return this.readBytes(length).wrap() +} \ No newline at end of file diff --git a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/ping_java.kt b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/ping_java.kt index d27aa04..2f7eda5 100644 --- a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/ping_java.kt +++ b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/java/ping_java.kt @@ -7,13 +7,12 @@ package cn.rtast.libmc.mcping.java -import cn.rtast.libmc.common.* +import cn.rtast.libmc.LibMCContext +import cn.rtast.libmc.mcping.PingResponse +import cn.rtast.libmc.mcping.sendPacket import cn.rtast.libmc.primitives.McStringCodec import cn.rtast.libmc.primitives.VarIntCodec import cn.rtast.libmc.stream.Socket -import cn.rtast.libmc.stream.readPacketFrame -import cn.rtast.libmc.mcping.PingResponse -import cn.rtast.libmc.mcping.sendPacket import kotlin.time.Clock internal suspend fun pingJavaServer(host: String, port: Int, context: LibMCContext): PingResponse { diff --git a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/mcping.kt b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/mcping.kt index 1832f71..d4fe59d 100644 --- a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/mcping.kt +++ b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/mcping.kt @@ -9,7 +9,7 @@ package cn.rtast.libmc.mcping -import cn.rtast.libmc.common.LibMCContext +import cn.rtast.libmc.LibMCContext import cn.rtast.libmc.mcping.bedrock.pingBedrockServer import cn.rtast.libmc.mcping.java.pingJavaServer import kotlin.jvm.JvmName diff --git a/libmc-protocol-encrypt/src/jvmTest/kotlin/test/TestJvmClient.kt b/libmc-protocol-encrypt/src/jvmTest/kotlin/test/TestJvmClient.kt index 29d003a..5ef1b92 100644 --- a/libmc-protocol-encrypt/src/jvmTest/kotlin/test/TestJvmClient.kt +++ b/libmc-protocol-encrypt/src/jvmTest/kotlin/test/TestJvmClient.kt @@ -33,7 +33,7 @@ class TestJvmClient { ) // cli.on { println(it) } // cli.on { println(it) } - cli.on { println(it) } + cli.onPacket { println(it) } cli.launch { cli.connect() } while (true) { } diff --git a/libmc-protocol/build.gradle.kts b/libmc-protocol/build.gradle.kts index 850457c..e7b43ec 100644 --- a/libmc-protocol/build.gradle.kts +++ b/libmc-protocol/build.gradle.kts @@ -22,11 +22,24 @@ kotlin { commonTest.dependencies { implementation(kotlin("test")) + implementation(project(":protocol-encrypt")) implementation(libs.kotlinx.coroutines.test) } jvmTest.dependencies { - implementation(project(":protocol-encrypt")) + implementation(libs.ktor.client.okhttp) + } + + linuxTest.dependencies { + implementation(libs.ktor.client.curl) + } + + mingwTest.dependencies { + implementation(libs.ktor.client.winhttp) + } + + appleTest.dependencies { + implementation(libs.ktor.client.darwin) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt index 81fc019..e12e44d 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt @@ -39,11 +39,7 @@ public class MinecraftClient internal constructor( internal val stateMachine = ClientStateMachine() public val networkChannel: NetworkChannel = NetworkChannel( - host = host, - port = port, - context = context, - stateMachine = stateMachine, - cipherProvider = cryptoContext.cipherFactory + host, port, context, stateMachine, cryptoContext.cipherFactory, this ) private val internalPacketDispatcher = InternalPacketDispatcher(this, authProvider) @@ -57,13 +53,18 @@ public class MinecraftClient internal constructor( networkChannel.connect() startListening() networkChannel.sendPacket( - ServerboundHandshakePacket(protocolVersion, host, port.toUShort(), HandshakeIntent.LOGIN) + ServerboundHandshakePacket( + protocolVersion, + host, port.toUShort(), + HandshakeIntent.LOGIN + ) ) stateMachine.transitionTo(ProtocolState.LOGIN) networkChannel.sendPacket(ServerboundLoginStartPacket(username, uuid)) } public fun setCompression(threshold: Int): Unit = networkChannel.setCompression(threshold) + private fun startListening() { listenJob = launch { try { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt index c9181ae..2568636 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt @@ -28,13 +28,16 @@ import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundPongPlayPacket import cn.rtast.libmc.protocol.protocol.state.ProtocolState import cn.rtast.libmc.protocol.util.generateRandom16Bytes +/** + * Internal simple state machine trigger, + * Auto respond packets the server needed. + * Only including `Handshake`, `Login` and `Configuration` State + */ public class InternalPacketDispatcher( private val client: MinecraftClient, private val authProvider: AuthenticationProvider, ) { - private suspend fun dispatchEvent(packet: MinecraftPacket) = client.dispatch(packet) public suspend fun handleIncomingPackets(packet: MinecraftPacket) { - this.dispatchEvent(packet) when (packet) { // login is ClientboundDisconnectLoginPacket -> client.close() diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt index 7ff3ee0..0f4c264 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt @@ -4,23 +4,77 @@ * Date: 2026/9/5 */ - package cn.rtast.libmc.protocol.event import cn.rtast.libmc.packet.MinecraftPacket +import cn.rtast.libmc.protocol.protocol.PacketDirection +import kotlin.concurrent.Volatile import kotlin.reflect.KClass -public open class PacketEventDispatcher { - @PublishedApi - internal val eventHandlers: MutableMap, MutableList Unit>> = - mutableMapOf() +private typealias Handler = suspend (MinecraftPacket) -> Unit +private typealias DirectionalHandler = suspend (MinecraftPacket, PacketDirection) -> Unit - internal suspend fun dispatch(event: MinecraftPacket) { - eventHandlers[event::class]?.forEach { it.invoke(event) } +public open class PacketEventDispatcher { + @Volatile + @PublishedApi + internal var receiveHandlers: Map, List> = emptyMap() + + @Volatile + @PublishedApi + internal var sentHandlers: Map, List> = emptyMap() + + @Volatile + @PublishedApi + internal var globalHandlers: List = emptyList() + + @PublishedApi + internal fun addTypedHandler( + isReceive: Boolean, + key: KClass, + handler: Handler, + ) { + if (isReceive) { + val current = receiveHandlers[key] ?: emptyList() + receiveHandlers = receiveHandlers + (key to (current + handler)) + } else { + val current = sentHandlers[key] ?: emptyList() + sentHandlers = sentHandlers + (key to (current + handler)) + } } - public inline fun on(crossinline block: suspend (T) -> Unit) { - val handlers = eventHandlers.getOrPut(T::class) { mutableListOf() } - handlers.add { event -> block(event as T) } + private suspend fun dispatch( + handlersMap: Map, List>, + packet: MinecraftPacket, + direction: PacketDirection, + ) { + handlersMap[packet::class]?.forEach { handler -> handler(packet) } + globalHandlers.forEach { handler -> handler(packet, direction) } + } + + internal suspend fun dispatchReceive(packet: MinecraftPacket) = + dispatch(receiveHandlers, packet, PacketDirection.CLIENTBOUND) + + internal suspend fun dispatchSent(packet: MinecraftPacket) = + dispatch(sentHandlers, packet, PacketDirection.SERVERBOUND) + + /** + * Lambda will be invoked when received a packet + */ + public inline fun onPacket(crossinline block: suspend (T) -> Unit) { + addTypedHandler(true, T::class) { block(it as T) } + } + + /** + * Lambda will be invoked after a packet sent + */ + public inline fun onSent(crossinline block: suspend (T) -> Unit) { + addTypedHandler(false, T::class) { block(it as T) } + } + + /** + * All packets will be appeared here, including `Outbound(Serverbound)` and `Inbound(Clientbound)` packet + */ + public fun on(block: suspend (packet: MinecraftPacket, direction: PacketDirection) -> Unit) { + globalHandlers = globalHandlers + block } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/CipherChannel.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/CipherChannel.kt index 50f00d7..75a6c9b 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/CipherChannel.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/CipherChannel.kt @@ -11,6 +11,9 @@ import cn.rtast.libmc.crypto.NetworkCipher import cn.rtast.libmc.stream.ReadChannel import cn.rtast.libmc.stream.WriteChannel +/** + * AES-128-CFB8 ***ciphered*** read channel + */ internal class CipherReadChannel( private val delegate: ReadChannel, private val crypto: NetworkCipher, @@ -34,6 +37,9 @@ internal class CipherReadChannel( } } +/** + * AES-128-CFB8 ***ciphered*** write channel + */ internal class CipherWriteChannel( private val delegate: WriteChannel, private val crypto: NetworkCipher, diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt index 813af50..a165c36 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt @@ -13,6 +13,7 @@ import cn.rtast.libmc.packet.writeBuffer import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.writeVarInt import cn.rtast.libmc.protocol.client.ClientStateMachine +import cn.rtast.libmc.protocol.event.PacketEventDispatcher import cn.rtast.libmc.protocol.protocol.GameProtocols import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.stream.wrap @@ -26,6 +27,7 @@ public class NetworkChannel internal constructor( context: LibMCContext, private val stateMachine: ClientStateMachine, cipherProvider: (ByteArray) -> NetworkCipher, + private val dispatcher: PacketEventDispatcher, ) { internal val session: NetworkSession = NetworkSession(host, port, context, cipherProvider) @@ -35,6 +37,11 @@ public class NetworkChannel internal constructor( public fun connect(): Unit = session.connect() public fun setCompression(threshold: Int): Unit = run { this.threshold = threshold } + /** + * This function receive all inbound packets, and dispatch as a packet event + * See [PacketEventDispatcher.dispatchReceive], + * Use [PacketEventDispatcher.onPacket] to get packet event + */ public suspend fun readNextPacket(): MinecraftPacket { val packetLength = session.readVarInt() val rawFrameBytes = session.readBytes(packetLength) @@ -46,11 +53,18 @@ public class NetworkChannel internal constructor( } val currentState = stateMachine.currentState val packetId = payloadBuf.readVarInt() - return GameProtocols.clientboundGameProtocols + val packet = GameProtocols.clientboundGameProtocols .getRegistry(currentState) .decodePacket(packetId, payloadBuf) + dispatcher.dispatchReceive(packet) + return packet } + /** + * This function will dispatch a packet event when packet was sent. + * See [PacketEventDispatcher.dispatchSent], + * Use [PacketEventDispatcher.onSent] to get packet event + */ public suspend fun sendPacket(packet: MinecraftPacket) { val uncompressedBodyBuf = BytesBuffer() GameProtocols.serverboundGameProtocols @@ -75,6 +89,7 @@ public class NetworkChannel internal constructor( frameBuffer.writeBuffer(contentBuf) } session.writeFully(frameBuffer.toByteArray()) + dispatcher.dispatchSent(packet) } public fun close(): Unit = session.close() diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/PacketDirection.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/PacketDirection.kt index 47bea6b..561bdc2 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/PacketDirection.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/PacketDirection.kt @@ -7,6 +7,6 @@ package cn.rtast.libmc.protocol.protocol -internal enum class PacketDirection { +public enum class PacketDirection { SERVERBOUND, CLIENTBOUND } \ No newline at end of file diff --git a/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt b/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt index d59a939..38e1a34 100644 --- a/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt +++ b/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt @@ -8,7 +8,6 @@ package test import cn.rtast.libmc.protocol.client.createMinecraftClient -import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundAwardStatisticsPacket import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -28,7 +27,7 @@ class TestClient { // rsaEncryptor = RSA1024Encryptor { data, sharedKey -> } } cli.launch { cli.connect() } - cli.on { println(it) } + cli.on { packet, direction -> println("${direction} -> $packet") } while (true) { } } diff --git a/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt b/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt index 9472017..b03fcb3 100644 --- a/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt +++ b/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt @@ -7,12 +7,11 @@ package test -import cn.rtast.libmc.protocol.client.createMinecraftClient +import cn.rtast.libmc.crypto.AuthenticationProvider import cn.rtast.libmc.crypto.RSA1024Encryptor import cn.rtast.libmc.crypto.Sha1Hasher -import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundLoginSuccessPacket -import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundSystemChatMessagePacket -import cn.rtast.libmc.crypto.AuthenticationProvider +import cn.rtast.libmc.protocol.client.createMinecraftClient +import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext import cn.rtast.libmc.protocol.util.generateOfflineUuid import kotlinx.coroutines.launch import org.junit.Test @@ -28,7 +27,6 @@ import kotlin.uuid.Uuid class TestClientTestInJvm { - val accessToken = File("src/jvmTest/resources/accessToken.txt").readText() fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray { @@ -63,25 +61,27 @@ class TestClientTestInJvm { // generateOfflineUuid("RTAkland"), Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"), // null, - accessToken - ) { - rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) } - sha1Hasher = - Sha1Hasher { serverId, secretKey, publicKey -> minecraftServerIdHash(serverId, secretKey, publicKey) } - cipherFactory = { key -> JvmAesCipher(key) } - - authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> - val connection = URL(url).openConnection() as HttpURLConnection - connection.requestMethod = "POST" - connection.doOutput = true - connection.setRequestProperty("Content-Type", "application/json") - connection.getOutputStream() - .use { it.write("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}".encodeToByteArray()) } - connection.disconnect() - } - } - cli.on { println(it) } - cli.on { println(it) } + accessToken, + crypto = DefaultProtocolContext + ) +// { +// rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) } +// sha1Hasher = +// Sha1Hasher { serverId, secretKey, publicKey -> minecraftServerIdHash(serverId, secretKey, publicKey) } +// cipherFactory = { key -> JvmAesCipher(key) } +// authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> +// val connection = URL(url).openConnection() as HttpURLConnection +// connection.requestMethod = "POST" +// connection.doOutput = true +// connection.setRequestProperty("Content-Type", "application/json") +// connection.getOutputStream() +// .use { it.write("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}".encodeToByteArray()) } +// connection.disconnect() +// } +// } +// cli.onPacket { println(it) } +// cli.onPacket { println(it) } + cli.on { packet, direction -> println("${direction} -> $packet") } cli.launch { cli.connect() } while (true) { } diff --git a/libmc-rconlib/src/commonMain/kotlin/cn/rtast/libmc/mcping/rconlib/rconlib.kt b/libmc-rconlib/src/commonMain/kotlin/cn/rtast/libmc/mcping/rconlib/rconlib.kt index 26a23e8..df29acc 100644 --- a/libmc-rconlib/src/commonMain/kotlin/cn/rtast/libmc/mcping/rconlib/rconlib.kt +++ b/libmc-rconlib/src/commonMain/kotlin/cn/rtast/libmc/mcping/rconlib/rconlib.kt @@ -8,7 +8,7 @@ package cn.rtast.libmc.mcping.rconlib -import cn.rtast.libmc.common.LibMCContext +import cn.rtast.libmc.LibMCContext import cn.rtast.libmc.stream.ReadChannel import cn.rtast.libmc.stream.Socket import cn.rtast.libmc.stream.WriteChannel diff --git a/settings.gradle.kts b/settings.gradle.kts index bc97c04..d197d81 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,7 +10,7 @@ includeSubModule(":rconlib") includeSubModule(":protocol") includeSubModule(":protocol-encrypt") includeSubModule(":nbt") -includeSubModule(":snbt") +//includeSubModule(":snbt") fun includeSubModule(name: String, path: String? = null) = include(name).also { project(name).projectDir = file(path ?: "libmc-${name.removePrefix(":")}")