Added documents

This commit is contained in:
2026-09-08 11:13:37 +08:00
parent f82d1a1d8f
commit 08392f92b8
30 files changed
+1423 -132

No files matched your search

@@ -29,27 +29,23 @@ public class MinecraftClient internal constructor(
internal val accessToken: String?,
parentJob: Job?,
private val ioDispatcher: CoroutineDispatcher,
protocolContext: ProtocolContext,
internal val protocolContext: ProtocolContext,
) : PacketEventDispatcher(), CoroutineScope {
internal val rsa1024Encryptor = protocolContext.rsaEncryptor
internal val serverIdHasher = protocolContext.sha1Hasher
internal val authProvider = protocolContext.authProvider
internal val stateMachine = ClientStateMachine()
public val networkChannel: NetworkChannel = NetworkChannel(
host, port, stateMachine,
protocolContext.cipherFactory,
this, protocolContext
)
private val internalPacketDispatcher = InternalPacketDispatcher(this, authProvider)
private val internalPacketDispatcher = InternalPacketDispatcher(this)
private val clientJob = SupervisorJob(parentJob)
private var listenJob: Job? = null
public val isOnlineMode: Boolean get() = accessToken != null
public val isOnlineMode: Boolean = accessToken != null
public val transactionManager: TransactionIdManager = TransactionIdManager()
public suspend fun connect(protocolVersion: Int = 776) {
public suspend fun connect(protocolVersion: Int = CURRENT_MINECRAFT_PROTOCOL_VERSION) {
networkChannel.connect()
startListening()
networkChannel.sendPacket(
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.client
internal const val CURRENT_MINECRAFT_PROTOCOL_VERSION: Int = 776
@@ -7,7 +7,6 @@
package cn.rtast.libmc.protocol.event
import cn.rtast.libmc.crypto.AuthenticationProvider
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.protocol.client.MinecraftClient
import cn.rtast.libmc.protocol.packet.configuration.clientbound.*
@@ -33,10 +32,7 @@ import cn.rtast.libmc.protocol.util.generateRandom16Bytes
* Auto respond packets the server needed.
* Only including `Handshake`, `Login` and `Configuration` State
*/
public class InternalPacketDispatcher(
private val client: MinecraftClient,
private val authProvider: AuthenticationProvider?,
) {
public class InternalPacketDispatcher(private val client: MinecraftClient) {
public suspend fun handleIncomingPackets(packet: MinecraftPacket) {
when (packet) {
// login
@@ -50,16 +46,18 @@ public class InternalPacketDispatcher(
is ClientboundHelloPacket -> {
val sharedSecret = generateRandom16Bytes()
if (client.isOnlineMode) {
val serverHash = client.serverIdHasher.hash(packet.serverId, sharedSecret, packet.publicKey)
authProvider!!.joinServer(
val serverHash = client.protocolContext.sha1Hasher!!
.hash(packet.serverId, sharedSecret, packet.publicKey)
client.protocolContext.authProvider!!.joinServer(
"https://sessionserver.mojang.com/session/minecraft/join",
client.accessToken!!,
client.uuid.toString().replace("-", ""),
serverHash
)
}
val encryptedSecret = client.rsa1024Encryptor.encrypt(packet.publicKey, sharedSecret)
val encryptedVerifyToken = client.rsa1024Encryptor.encrypt(packet.publicKey, packet.verifyToken)
val encryptedSecret = client.protocolContext.rsaEncryptor!!.encrypt(packet.publicKey, sharedSecret)
val encryptedVerifyToken =
client.protocolContext.rsaEncryptor!!.encrypt(packet.publicKey, packet.verifyToken)
client.networkChannel.sendPacket(ServerboundKeyPacket(encryptedSecret, encryptedVerifyToken))
client.networkChannel.session.enableEncryption(sharedSecret)
}
@@ -26,11 +26,10 @@ public class NetworkChannel internal constructor(
host: String,
port: Int,
private val stateMachine: ClientStateMachine,
cipherProvider: (ByteArray) -> NetworkCipher,
private val dispatcher: PacketEventDispatcher,
protocolContext: ProtocolContext,
) {
internal val session: NetworkSession = NetworkSession(host, port, cipherProvider, protocolContext)
internal val session: NetworkSession = NetworkSession(host, port, protocolContext)
@Volatile
private var threshold = -1
@@ -6,7 +6,6 @@
package cn.rtast.libmc.protocol.network
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.crypto.ProtocolContext
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
@@ -16,7 +15,6 @@ import cn.rtast.libmc.primitives.readVarInt
public class NetworkSession internal constructor(
private val host: String,
private val port: Int,
private var cipherProvider: (ByteArray) -> NetworkCipher,
private val context: ProtocolContext,
) {
private var socket: RawSocket? = null
@@ -38,7 +36,7 @@ public class NetworkSession internal constructor(
public fun enableEncryption(sharedKey: ByteArray) {
val currentRead = requireNotNull(readChannel)
val currentWrite = requireNotNull(writeChannel)
val cipher = cipherProvider(sharedKey)
val cipher = context.cipherFactory!!.invoke(sharedKey)
this.readChannel = CipherReadChannel(currentRead, cipher)
this.writeChannel = CipherWriteChannel(currentWrite, cipher)
}
@@ -10,6 +10,10 @@ package cn.rtast.libmc.protocol.util
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Client-side managed transaction id manager,
* managed an auto-increment transaction id
*/
public class TransactionIdManager internal constructor() {
private var queryTransactionCounter: Int = 1
private var commandSuggestionTransactionCounter: Int = 1
@@ -0,0 +1,89 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package test
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.SocketEngine
import cn.rtast.libmc.network.WriteChannel
import java.io.InputStream
import java.io.OutputStream
import java.net.InetSocketAddress
import java.net.Socket
class JavaSocketEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = JavaNetworkSocket(host, port)
}
class JavaNetworkSocket(
private val host: String,
private val port: Int,
private val connectTimeoutMs: Int = 10000,
) : RawSocket {
private lateinit var socket: Socket
private lateinit var readChannel: JavaReadChannel
private lateinit var writeChannel: JavaWriteChannel
override suspend fun connect() {
val s = Socket()
s.tcpNoDelay = true
s.connect(InetSocketAddress(host, port), connectTimeoutMs)
socket = s
readChannel = JavaReadChannel(s.getInputStream())
writeChannel = JavaWriteChannel(s.getOutputStream())
}
override fun openReadChannel(): ReadChannel = readChannel
override fun openWriteChannel(): WriteChannel = writeChannel
override fun close() {
if (::socket.isInitialized && !socket.isClosed) {
runCatching { socket.close() }
}
}
}
class JavaReadChannel(private val inputStream: InputStream) : ReadChannel {
override suspend fun readByte(): Byte {
val b = inputStream.read()
if (b == -1) throw IllegalStateException("Socket stream reached EOF while reading byte")
return b.toByte()
}
override suspend fun readBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
readFullyInternal(buffer, 0, length)
return buffer
}
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
readFullyInternal(out, start, end - start)
}
private fun readFullyInternal(out: ByteArray, offset: Int, length: Int) {
var bytesRead = 0
while (bytesRead < length) {
val count = inputStream.read(out, offset + bytesRead, length - bytesRead)
if (count == -1) {
throw IllegalStateException("Socket stream closed unexpectedly (read $bytesRead of $length bytes)")
}
bytesRead += count
}
}
}
class JavaWriteChannel(private val outputStream: OutputStream) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
outputStream.write(value, startIndex, endIndex - startIndex)
}
override suspend fun flush() {
outputStream.flush()
}
}
@@ -17,11 +17,6 @@ import cn.rtast.libmc.protocol.util.generateOfflineUuid
import kotlinx.coroutines.launch
import org.junit.Test
import java.io.File
import java.math.BigInteger
import java.security.KeyFactory
import java.security.MessageDigest
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
import kotlin.random.Random
import kotlin.time.Clock
import kotlin.uuid.Uuid
@@ -31,43 +26,14 @@ class TestClientTestInJvm {
val accessToken = File("src/jvmTest/resources/accessToken.txt").readText()
private val chatTracker = ClientChatTracker()
fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
val keySpec = X509EncodedKeySpec(publicKeyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
fun minecraftServerIdHash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String {
val serverIdBytes: ByteArray = serverId.encodeToByteArray()
for (b in serverIdBytes) {
require((b.toInt() and 0xFF) <= 0x7F) { "serverId contains non-US-ASCII character" }
}
val data = ByteArray(serverIdBytes.size + secretKey.size + publicKey.size)
System.arraycopy(serverIdBytes, 0, data, 0, serverIdBytes.size)
System.arraycopy(secretKey, 0, data, serverIdBytes.size, secretKey.size)
System.arraycopy(publicKey, 0, data, serverIdBytes.size + secretKey.size, publicKey.size)
val digest = MessageDigest.getInstance("SHA-1")
val hash = digest.digest(data)
return BigInteger(hash).toString(16)
}
@Test
fun `test client`() {
val cli = createMinecraftClient(
"127.0.0.1",
25565,
"RTAkland",
// generateOfflineUuid("RTAkland"),
"127.0.0.1", 25565, "RTAkland",
Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
// null,
accessToken,
context = DefaultProtocolContext
)
// cli.onPacket<ClientboundSystemChatMessagePacket> { println(it) }
// cli.onPacket<ClientboundLoginSuccessPacket> { println(it) }
cli.on { packet, direction -> println("$direction -> $packet") }
cli.launch { cli.connect() }
while (true) {
@@ -77,11 +43,8 @@ class TestClientTestInJvm {
@Test
fun `test client offline mode`() {
val cli = createMinecraftClient(
"127.0.0.1",
25566,
"11",
generateOfflineUuid("11"),
null,
"127.0.0.1", 25566, "11",
generateOfflineUuid("11"), null,
context = DefaultProtocolContext.withCustom {
socketEngine = KtorNetworkEngine()
}