Added documents
This commit is contained in:
30 files changed
+1423
-132
No files matched your search
+4
-8
@@ -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(
|
||||
|
||||
+10
@@ -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
-9
@@ -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)
|
||||
}
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
+1
-3
@@ -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)
|
||||
}
|
||||
|
||||
+4
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user