Add all packets handler
This commit is contained in:
25 files changed
+202
-105
No files matched your search
+7
-6
@@ -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 {
|
||||
|
||||
+5
-2
@@ -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()
|
||||
|
||||
+64
-10
@@ -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<KClass<out MinecraftPacket>, MutableList<suspend (MinecraftPacket) -> 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<KClass<out MinecraftPacket>, List<Handler>> = emptyMap()
|
||||
|
||||
@Volatile
|
||||
@PublishedApi
|
||||
internal var sentHandlers: Map<KClass<out MinecraftPacket>, List<Handler>> = emptyMap()
|
||||
|
||||
@Volatile
|
||||
@PublishedApi
|
||||
internal var globalHandlers: List<DirectionalHandler> = emptyList()
|
||||
|
||||
@PublishedApi
|
||||
internal fun <T : MinecraftPacket> addTypedHandler(
|
||||
isReceive: Boolean,
|
||||
key: KClass<T>,
|
||||
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 <reified T : MinecraftPacket> 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<KClass<out MinecraftPacket>, List<Handler>>,
|
||||
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 <reified T : MinecraftPacket> 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 <reified T : MinecraftPacket> 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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
+16
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -7,6 +7,6 @@
|
||||
|
||||
package cn.rtast.libmc.protocol.protocol
|
||||
|
||||
internal enum class PacketDirection {
|
||||
public enum class PacketDirection {
|
||||
SERVERBOUND, CLIENTBOUND
|
||||
}
|
||||
@@ -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<ClientboundAwardStatisticsPacket> { println(it) }
|
||||
cli.on { packet, direction -> println("${direction} -> $packet") }
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ClientboundSystemChatMessagePacket> { println(it) }
|
||||
cli.on<ClientboundLoginSuccessPacket> { 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<ClientboundSystemChatMessagePacket> { println(it) }
|
||||
// cli.onPacket<ClientboundLoginSuccessPacket> { println(it) }
|
||||
cli.on { packet, direction -> println("${direction} -> $packet") }
|
||||
cli.launch { cli.connect() }
|
||||
while (true) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user