Add all packets handler
This commit is contained in:
25 files changed
+202
-105
No files matched your search
@@ -13,6 +13,8 @@ A lightweight minecraft client-side protocol library and related library, includ
|
|||||||
|
|
||||||
# libmc-protocol
|
# libmc-protocol
|
||||||
|
|
||||||
|
[Use libmc-protocol](https://repo.rtast.cn/packages/-/cn.rtast.libmc:protocol)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Minecraft Protocol Library Status & Roadmap
|
# Minecraft Protocol Library Status & Roadmap
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ allprojects {
|
|||||||
group = "cn.rtast.libmc"
|
group = "cn.rtast.libmc"
|
||||||
val libVersion = getProperty("libVersion")
|
val libVersion = getProperty("libVersion")
|
||||||
version = when (name) {
|
version = when (name) {
|
||||||
"protocol" -> "${getProperty("protocolVersion")}-$libVersion"
|
"protocol" -> getProperty("protocolVersion")
|
||||||
else -> libVersion
|
else -> libVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,13 @@
|
|||||||
* **Auth Join Request** (`AuthenticationProvider`): Sends the HTTP POST request to Mojang's session server
|
* **Auth Join Request** (`AuthenticationProvider`): Sends the HTTP POST request to Mojang's session server
|
||||||
(`https://sessionserver.mojang.com/session/minecraft/join`).
|
(`https://sessionserver.mojang.com/session/minecraft/join`).
|
||||||
|
|
||||||
|
[Use libmc-protocol-encrypt](https://repo.rtast.cn/packages/-/cn.rtast.libmc:protocol-encrypt)
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val client = createMinecraftClient(
|
fun main() {
|
||||||
host = "127.0.0.1",
|
val client = createMinecraftClient(
|
||||||
username = "Player",
|
// other parameter
|
||||||
accessToken = accessToken
|
crypto = DefaultProtocolContext
|
||||||
) {
|
)
|
||||||
// 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.) */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
```
|
||||||
+2
-2
@@ -3,5 +3,5 @@ kotlin.native.ignoreDisabledTargets=true
|
|||||||
kotlin.native.enableKlibsCrossCompilation=true
|
kotlin.native.enableKlibsCrossCompilation=true
|
||||||
kotlin.daemon.jvmargs=-Xmx2048M
|
kotlin.daemon.jvmargs=-Xmx2048M
|
||||||
org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8
|
||||||
libVersion=0.1.0
|
libVersion=0.1.1
|
||||||
protocolVersion=26.2
|
protocolVersion=26.2-0.1.1
|
||||||
@@ -6,6 +6,7 @@ coroutines-test = "1.11.0"
|
|||||||
kotlinx-coroutines = "1.11.0"
|
kotlinx-coroutines = "1.11.0"
|
||||||
ktor-core = "3.5.2"
|
ktor-core = "3.5.2"
|
||||||
cryptography-core = "0.6.0"
|
cryptography-core = "0.6.0"
|
||||||
|
kotlinx-atomicfu = "0.33.0"
|
||||||
#kotlinx-serialization = "1.11.0"
|
#kotlinx-serialization = "1.11.0"
|
||||||
|
|
||||||
[libraries]
|
[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-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-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor-core" }
|
||||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", 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-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" }
|
#kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
maven-publish = { id = "maven-publish" }
|
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" }
|
#kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
@@ -15,9 +15,7 @@ public class PacketRegistry {
|
|||||||
private val idToCodec = mutableMapOf<Int, PacketCodec<out MinecraftPacket>>()
|
private val idToCodec = mutableMapOf<Int, PacketCodec<out MinecraftPacket>>()
|
||||||
private val classToInfo = mutableMapOf<KClass<out MinecraftPacket>, RegisteredPacket<*>>()
|
private val classToInfo = mutableMapOf<KClass<out MinecraftPacket>, RegisteredPacket<*>>()
|
||||||
|
|
||||||
private data class RegisteredPacket<P : MinecraftPacket>(
|
private data class RegisteredPacket<P : MinecraftPacket>(val id: Int, val codec: PacketCodec<P>)
|
||||||
val id: Int, val codec: PacketCodec<P>,
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <T : MinecraftPacket> register(id: Int, kClass: KClass<T>, codec: PacketCodec<T>) {
|
public fun <T : MinecraftPacket> register(id: Int, kClass: KClass<T>, codec: PacketCodec<T>) {
|
||||||
idToCodec[id] = codec
|
idToCodec[id] = codec
|
||||||
|
|||||||
@@ -40,8 +40,3 @@ public expect class BytesBuffer {
|
|||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
public inline fun ByteArray.wrap(): BytesBuffer = BytesBuffer(this)
|
public inline fun ByteArray.wrap(): BytesBuffer = BytesBuffer(this)
|
||||||
|
|
||||||
public suspend fun ReadChannel.readPacketFrame(): BytesBuffer {
|
|
||||||
val length = this.readVarInt()
|
|
||||||
return this.readBytes(length).wrap()
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.stream
|
package cn.rtast.libmc.stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform specified raw byte read channel
|
||||||
|
*/
|
||||||
public expect open class ReadChannel() {
|
public expect open class ReadChannel() {
|
||||||
public open suspend fun readByte(): Byte
|
public open suspend fun readByte(): Byte
|
||||||
public open suspend fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
|
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)
|
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 expect open class WriteChannel() {
|
||||||
public open suspend fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
|
public open suspend fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
|
||||||
public open suspend fun flush()
|
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
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.stream
|
package cn.rtast.libmc.stream
|
||||||
|
|
||||||
import cn.rtast.libmc.common.LibMCContext
|
import cn.rtast.libmc.LibMCContext
|
||||||
import io.ktor.network.sockets.*
|
import io.ktor.network.sockets.*
|
||||||
import io.ktor.utils.io.core.*
|
import io.ktor.utils.io.core.*
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.mcping.bedrock
|
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.UdpSocket
|
||||||
import cn.rtast.libmc.stream.wrap
|
import cn.rtast.libmc.stream.wrap
|
||||||
import cn.rtast.libmc.mcping.PingResponse
|
import cn.rtast.libmc.mcping.PingResponse
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -7,13 +7,12 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.mcping.java
|
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.McStringCodec
|
||||||
import cn.rtast.libmc.primitives.VarIntCodec
|
import cn.rtast.libmc.primitives.VarIntCodec
|
||||||
import cn.rtast.libmc.stream.Socket
|
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
|
import kotlin.time.Clock
|
||||||
|
|
||||||
internal suspend fun pingJavaServer(host: String, port: Int, context: LibMCContext): PingResponse {
|
internal suspend fun pingJavaServer(host: String, port: Int, context: LibMCContext): PingResponse {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.mcping
|
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.bedrock.pingBedrockServer
|
||||||
import cn.rtast.libmc.mcping.java.pingJavaServer
|
import cn.rtast.libmc.mcping.java.pingJavaServer
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TestJvmClient {
|
|||||||
)
|
)
|
||||||
// cli.on<ClientboundSystemChatMessagePacket> { println(it) }
|
// cli.on<ClientboundSystemChatMessagePacket> { println(it) }
|
||||||
// cli.on<ClientboundLoginSuccessPacket> { println(it) }
|
// cli.on<ClientboundLoginSuccessPacket> { println(it) }
|
||||||
cli.on<MinecraftPacket> { println(it) }
|
cli.onPacket<MinecraftPacket> { println(it) }
|
||||||
cli.launch { cli.connect() }
|
cli.launch { cli.connect() }
|
||||||
while (true) {
|
while (true) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,24 @@ kotlin {
|
|||||||
|
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
|
implementation(project(":protocol-encrypt"))
|
||||||
implementation(libs.kotlinx.coroutines.test)
|
implementation(libs.kotlinx.coroutines.test)
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmTest.dependencies {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-6
@@ -39,11 +39,7 @@ public class MinecraftClient internal constructor(
|
|||||||
internal val stateMachine = ClientStateMachine()
|
internal val stateMachine = ClientStateMachine()
|
||||||
|
|
||||||
public val networkChannel: NetworkChannel = NetworkChannel(
|
public val networkChannel: NetworkChannel = NetworkChannel(
|
||||||
host = host,
|
host, port, context, stateMachine, cryptoContext.cipherFactory, this
|
||||||
port = port,
|
|
||||||
context = context,
|
|
||||||
stateMachine = stateMachine,
|
|
||||||
cipherProvider = cryptoContext.cipherFactory
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val internalPacketDispatcher = InternalPacketDispatcher(this, authProvider)
|
private val internalPacketDispatcher = InternalPacketDispatcher(this, authProvider)
|
||||||
@@ -57,13 +53,18 @@ public class MinecraftClient internal constructor(
|
|||||||
networkChannel.connect()
|
networkChannel.connect()
|
||||||
startListening()
|
startListening()
|
||||||
networkChannel.sendPacket(
|
networkChannel.sendPacket(
|
||||||
ServerboundHandshakePacket(protocolVersion, host, port.toUShort(), HandshakeIntent.LOGIN)
|
ServerboundHandshakePacket(
|
||||||
|
protocolVersion,
|
||||||
|
host, port.toUShort(),
|
||||||
|
HandshakeIntent.LOGIN
|
||||||
|
)
|
||||||
)
|
)
|
||||||
stateMachine.transitionTo(ProtocolState.LOGIN)
|
stateMachine.transitionTo(ProtocolState.LOGIN)
|
||||||
networkChannel.sendPacket(ServerboundLoginStartPacket(username, uuid))
|
networkChannel.sendPacket(ServerboundLoginStartPacket(username, uuid))
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun setCompression(threshold: Int): Unit = networkChannel.setCompression(threshold)
|
public fun setCompression(threshold: Int): Unit = networkChannel.setCompression(threshold)
|
||||||
|
|
||||||
private fun startListening() {
|
private fun startListening() {
|
||||||
listenJob = launch {
|
listenJob = launch {
|
||||||
try {
|
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.protocol.state.ProtocolState
|
||||||
import cn.rtast.libmc.protocol.util.generateRandom16Bytes
|
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(
|
public class InternalPacketDispatcher(
|
||||||
private val client: MinecraftClient,
|
private val client: MinecraftClient,
|
||||||
private val authProvider: AuthenticationProvider,
|
private val authProvider: AuthenticationProvider,
|
||||||
) {
|
) {
|
||||||
private suspend fun dispatchEvent(packet: MinecraftPacket) = client.dispatch(packet)
|
|
||||||
public suspend fun handleIncomingPackets(packet: MinecraftPacket) {
|
public suspend fun handleIncomingPackets(packet: MinecraftPacket) {
|
||||||
this.dispatchEvent(packet)
|
|
||||||
when (packet) {
|
when (packet) {
|
||||||
// login
|
// login
|
||||||
is ClientboundDisconnectLoginPacket -> client.close()
|
is ClientboundDisconnectLoginPacket -> client.close()
|
||||||
|
|||||||
+64
-10
@@ -4,23 +4,77 @@
|
|||||||
* Date: 2026/9/5
|
* Date: 2026/9/5
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.protocol.event
|
package cn.rtast.libmc.protocol.event
|
||||||
|
|
||||||
import cn.rtast.libmc.packet.MinecraftPacket
|
import cn.rtast.libmc.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.protocol.protocol.PacketDirection
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public open class PacketEventDispatcher {
|
private typealias Handler = suspend (MinecraftPacket) -> Unit
|
||||||
@PublishedApi
|
private typealias DirectionalHandler = suspend (MinecraftPacket, PacketDirection) -> Unit
|
||||||
internal val eventHandlers: MutableMap<KClass<out MinecraftPacket>, MutableList<suspend (MinecraftPacket) -> Unit>> =
|
|
||||||
mutableMapOf()
|
|
||||||
|
|
||||||
internal suspend fun dispatch(event: MinecraftPacket) {
|
public open class PacketEventDispatcher {
|
||||||
eventHandlers[event::class]?.forEach { it.invoke(event) }
|
@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) {
|
private suspend fun dispatch(
|
||||||
val handlers = eventHandlers.getOrPut(T::class) { mutableListOf() }
|
handlersMap: Map<KClass<out MinecraftPacket>, List<Handler>>,
|
||||||
handlers.add { event -> block(event as T) }
|
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.ReadChannel
|
||||||
import cn.rtast.libmc.stream.WriteChannel
|
import cn.rtast.libmc.stream.WriteChannel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-128-CFB8 ***ciphered*** read channel
|
||||||
|
*/
|
||||||
internal class CipherReadChannel(
|
internal class CipherReadChannel(
|
||||||
private val delegate: ReadChannel,
|
private val delegate: ReadChannel,
|
||||||
private val crypto: NetworkCipher,
|
private val crypto: NetworkCipher,
|
||||||
@@ -34,6 +37,9 @@ internal class CipherReadChannel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-128-CFB8 ***ciphered*** write channel
|
||||||
|
*/
|
||||||
internal class CipherWriteChannel(
|
internal class CipherWriteChannel(
|
||||||
private val delegate: WriteChannel,
|
private val delegate: WriteChannel,
|
||||||
private val crypto: NetworkCipher,
|
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.readVarInt
|
||||||
import cn.rtast.libmc.primitives.writeVarInt
|
import cn.rtast.libmc.primitives.writeVarInt
|
||||||
import cn.rtast.libmc.protocol.client.ClientStateMachine
|
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.protocol.protocol.GameProtocols
|
||||||
import cn.rtast.libmc.stream.BytesBuffer
|
import cn.rtast.libmc.stream.BytesBuffer
|
||||||
import cn.rtast.libmc.stream.wrap
|
import cn.rtast.libmc.stream.wrap
|
||||||
@@ -26,6 +27,7 @@ public class NetworkChannel internal constructor(
|
|||||||
context: LibMCContext,
|
context: LibMCContext,
|
||||||
private val stateMachine: ClientStateMachine,
|
private val stateMachine: ClientStateMachine,
|
||||||
cipherProvider: (ByteArray) -> NetworkCipher,
|
cipherProvider: (ByteArray) -> NetworkCipher,
|
||||||
|
private val dispatcher: PacketEventDispatcher,
|
||||||
) {
|
) {
|
||||||
internal val session: NetworkSession = NetworkSession(host, port, context, cipherProvider)
|
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 connect(): Unit = session.connect()
|
||||||
public fun setCompression(threshold: Int): Unit = run { this.threshold = threshold }
|
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 {
|
public suspend fun readNextPacket(): MinecraftPacket {
|
||||||
val packetLength = session.readVarInt()
|
val packetLength = session.readVarInt()
|
||||||
val rawFrameBytes = session.readBytes(packetLength)
|
val rawFrameBytes = session.readBytes(packetLength)
|
||||||
@@ -46,11 +53,18 @@ public class NetworkChannel internal constructor(
|
|||||||
}
|
}
|
||||||
val currentState = stateMachine.currentState
|
val currentState = stateMachine.currentState
|
||||||
val packetId = payloadBuf.readVarInt()
|
val packetId = payloadBuf.readVarInt()
|
||||||
return GameProtocols.clientboundGameProtocols
|
val packet = GameProtocols.clientboundGameProtocols
|
||||||
.getRegistry(currentState)
|
.getRegistry(currentState)
|
||||||
.decodePacket(packetId, payloadBuf)
|
.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) {
|
public suspend fun sendPacket(packet: MinecraftPacket) {
|
||||||
val uncompressedBodyBuf = BytesBuffer()
|
val uncompressedBodyBuf = BytesBuffer()
|
||||||
GameProtocols.serverboundGameProtocols
|
GameProtocols.serverboundGameProtocols
|
||||||
@@ -75,6 +89,7 @@ public class NetworkChannel internal constructor(
|
|||||||
frameBuffer.writeBuffer(contentBuf)
|
frameBuffer.writeBuffer(contentBuf)
|
||||||
}
|
}
|
||||||
session.writeFully(frameBuffer.toByteArray())
|
session.writeFully(frameBuffer.toByteArray())
|
||||||
|
dispatcher.dispatchSent(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun close(): Unit = session.close()
|
public fun close(): Unit = session.close()
|
||||||
|
|||||||
+1
-1
@@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.protocol.protocol
|
package cn.rtast.libmc.protocol.protocol
|
||||||
|
|
||||||
internal enum class PacketDirection {
|
public enum class PacketDirection {
|
||||||
SERVERBOUND, CLIENTBOUND
|
SERVERBOUND, CLIENTBOUND
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import cn.rtast.libmc.protocol.client.createMinecraftClient
|
import cn.rtast.libmc.protocol.client.createMinecraftClient
|
||||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundAwardStatisticsPacket
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@@ -28,7 +27,7 @@ class TestClient {
|
|||||||
// rsaEncryptor = RSA1024Encryptor { data, sharedKey -> }
|
// rsaEncryptor = RSA1024Encryptor { data, sharedKey -> }
|
||||||
}
|
}
|
||||||
cli.launch { cli.connect() }
|
cli.launch { cli.connect() }
|
||||||
cli.on<ClientboundAwardStatisticsPacket> { println(it) }
|
cli.on { packet, direction -> println("${direction} -> $packet") }
|
||||||
while (true) {
|
while (true) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,11 @@
|
|||||||
|
|
||||||
package test
|
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.RSA1024Encryptor
|
||||||
import cn.rtast.libmc.crypto.Sha1Hasher
|
import cn.rtast.libmc.crypto.Sha1Hasher
|
||||||
import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundLoginSuccessPacket
|
import cn.rtast.libmc.protocol.client.createMinecraftClient
|
||||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundSystemChatMessagePacket
|
import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext
|
||||||
import cn.rtast.libmc.crypto.AuthenticationProvider
|
|
||||||
import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -28,7 +27,6 @@ import kotlin.uuid.Uuid
|
|||||||
|
|
||||||
|
|
||||||
class TestClientTestInJvm {
|
class TestClientTestInJvm {
|
||||||
|
|
||||||
val accessToken = File("src/jvmTest/resources/accessToken.txt").readText()
|
val accessToken = File("src/jvmTest/resources/accessToken.txt").readText()
|
||||||
|
|
||||||
fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
|
fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
|
||||||
@@ -63,25 +61,27 @@ class TestClientTestInJvm {
|
|||||||
// generateOfflineUuid("RTAkland"),
|
// generateOfflineUuid("RTAkland"),
|
||||||
Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
|
Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
|
||||||
// null,
|
// null,
|
||||||
accessToken
|
accessToken,
|
||||||
) {
|
crypto = DefaultProtocolContext
|
||||||
rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) }
|
)
|
||||||
sha1Hasher =
|
// {
|
||||||
Sha1Hasher { serverId, secretKey, publicKey -> minecraftServerIdHash(serverId, secretKey, publicKey) }
|
// rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) }
|
||||||
cipherFactory = { key -> JvmAesCipher(key) }
|
// sha1Hasher =
|
||||||
|
// Sha1Hasher { serverId, secretKey, publicKey -> minecraftServerIdHash(serverId, secretKey, publicKey) }
|
||||||
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash ->
|
// cipherFactory = { key -> JvmAesCipher(key) }
|
||||||
val connection = URL(url).openConnection() as HttpURLConnection
|
// authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash ->
|
||||||
connection.requestMethod = "POST"
|
// val connection = URL(url).openConnection() as HttpURLConnection
|
||||||
connection.doOutput = true
|
// connection.requestMethod = "POST"
|
||||||
connection.setRequestProperty("Content-Type", "application/json")
|
// connection.doOutput = true
|
||||||
connection.getOutputStream()
|
// connection.setRequestProperty("Content-Type", "application/json")
|
||||||
.use { it.write("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}".encodeToByteArray()) }
|
// connection.getOutputStream()
|
||||||
connection.disconnect()
|
// .use { it.write("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}".encodeToByteArray()) }
|
||||||
}
|
// connection.disconnect()
|
||||||
}
|
// }
|
||||||
cli.on<ClientboundSystemChatMessagePacket> { println(it) }
|
// }
|
||||||
cli.on<ClientboundLoginSuccessPacket> { println(it) }
|
// cli.onPacket<ClientboundSystemChatMessagePacket> { println(it) }
|
||||||
|
// cli.onPacket<ClientboundLoginSuccessPacket> { println(it) }
|
||||||
|
cli.on { packet, direction -> println("${direction} -> $packet") }
|
||||||
cli.launch { cli.connect() }
|
cli.launch { cli.connect() }
|
||||||
while (true) {
|
while (true) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.mcping.rconlib
|
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.ReadChannel
|
||||||
import cn.rtast.libmc.stream.Socket
|
import cn.rtast.libmc.stream.Socket
|
||||||
import cn.rtast.libmc.stream.WriteChannel
|
import cn.rtast.libmc.stream.WriteChannel
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ includeSubModule(":rconlib")
|
|||||||
includeSubModule(":protocol")
|
includeSubModule(":protocol")
|
||||||
includeSubModule(":protocol-encrypt")
|
includeSubModule(":protocol-encrypt")
|
||||||
includeSubModule(":nbt")
|
includeSubModule(":nbt")
|
||||||
includeSubModule(":snbt")
|
//includeSubModule(":snbt")
|
||||||
|
|
||||||
fun includeSubModule(name: String, path: String? = null) = include(name).also {
|
fun includeSubModule(name: String, path: String? = null) = include(name).also {
|
||||||
project(name).projectDir = file(path ?: "libmc-${name.removePrefix(":")}")
|
project(name).projectDir = file(path ?: "libmc-${name.removePrefix(":")}")
|
||||||
|
|||||||
Reference in New Issue
Block a user