Make packets public and formatted, fix state machine change state incorrectly
This commit is contained in:
96 files changed
+1718
-873
No files matched your search
@@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.configuration.AckFinishConfigurationPacket
|
|
||||||
import cn.rtast.libmc.chat.packet.configuration.ServerboundPongPacket
|
|
||||||
import cn.rtast.libmc.chat.packet.configuration.ServerboundSelectKnownPacksPacket
|
|
||||||
import cn.rtast.libmc.chat.packet.handshake.HandshakePacket
|
|
||||||
import cn.rtast.libmc.chat.packet.login.LoginAcknowledgedPacket
|
|
||||||
import cn.rtast.libmc.chat.packet.login.LoginStartPacket
|
|
||||||
import cn.rtast.libmc.chat.packet.play.ServerboundKeepAlivePlayPacket
|
|
||||||
import cn.rtast.libmc.chat.protocol.HandshakeIntent
|
|
||||||
import cn.rtast.libmc.chat.protocol.ProtocolState
|
|
||||||
import cn.rtast.libmc.chat.util.generateOfflineUuid
|
|
||||||
import cn.rtast.libmc.common.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
|
|
||||||
public class MinecraftChatClient(
|
|
||||||
private val host: String,
|
|
||||||
private val port: Int,
|
|
||||||
private val username: String,
|
|
||||||
private val uuid: Uuid = generateOfflineUuid(username),
|
|
||||||
private val context: LibMCContext = LibMCContext(),
|
|
||||||
) {
|
|
||||||
private var state = ProtocolState.HANDSHAKE
|
|
||||||
|
|
||||||
public suspend fun start(): Unit = coroutineScope {
|
|
||||||
val socket = _Socket(host, port, context)
|
|
||||||
val input = socket.openReadChannel()
|
|
||||||
val output = socket.openWriteChannel()
|
|
||||||
|
|
||||||
executeInitHandshake(output)
|
|
||||||
|
|
||||||
val readerJob = launch(Dispatchers.Default) {
|
|
||||||
handleIncomingPackets(input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
readerJob.join()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun executeInitHandshake(output: _WriteChannel) {
|
|
||||||
val handshakePacket = HandshakePacket(776, host, port.toUShort(), HandshakeIntent.LOGIN)
|
|
||||||
output.sendPacket(handshakePacket, HandshakePacket)
|
|
||||||
state = ProtocolState.LOGIN
|
|
||||||
|
|
||||||
val loginStartPacket = LoginStartPacket(username, uuid)
|
|
||||||
output.sendPacket(loginStartPacket, LoginStartPacket)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun handleIncomingPackets(input: _ReadChannel, output: _WriteChannel) {
|
|
||||||
try {
|
|
||||||
while (currentCoroutineContext().isActive) {
|
|
||||||
val packetLength = input.readVarInt()
|
|
||||||
if (packetLength <= 0) continue
|
|
||||||
|
|
||||||
val packetBytes = ByteArray(packetLength)
|
|
||||||
input.readFully(packetBytes, 0, packetLength)
|
|
||||||
|
|
||||||
val buffer = _Buffer(packetBytes)
|
|
||||||
val packetId = buffer.readVarInt()
|
|
||||||
println("received -> State: $state | ID: 0x${packetId.toString(16).uppercase()} | Length: $packetLength")
|
|
||||||
try {
|
|
||||||
when (state) {
|
|
||||||
ProtocolState.LOGIN -> handleLoginPackets(packetId, output)
|
|
||||||
ProtocolState.CONFIGURATION -> handleConfigurationPackets(packetId, buffer, output)
|
|
||||||
ProtocolState.PLAY -> handlePlayPackets(packetId, buffer, output)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("parsing 0x${packetId.toString(16).uppercase()} Payload failed: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
println("disconnecting: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleLoginPackets(packetId: Int, output: _WriteChannel) {
|
|
||||||
when (packetId) {
|
|
||||||
0x02 -> {
|
|
||||||
output.sendPacket(LoginAcknowledgedPacket(), LoginAcknowledgedPacket)
|
|
||||||
state = ProtocolState.CONFIGURATION
|
|
||||||
println("[3/4] sent LoginAcknowledgedPacket -> switching to CONFIGURATION state")
|
|
||||||
|
|
||||||
output.sendPacket(
|
|
||||||
ServerboundSelectKnownPacksPacket(knownPacks = emptyList()),
|
|
||||||
ServerboundSelectKnownPacksPacket
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
0x00 -> {
|
|
||||||
println("login denied (ClientboundDisconnectLoginPacket)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleConfigurationPackets(packetId: Int, packetBuffer: _Buffer, output: _WriteChannel) {
|
|
||||||
when (packetId) {
|
|
||||||
0x0E -> {
|
|
||||||
println("received ClientboundSelectKnownPacksPacket")
|
|
||||||
}
|
|
||||||
|
|
||||||
0x03 -> {
|
|
||||||
output.sendPacket(AckFinishConfigurationPacket, AckFinishConfigurationPacket)
|
|
||||||
state = ProtocolState.PLAY
|
|
||||||
}
|
|
||||||
|
|
||||||
0x05 -> {
|
|
||||||
output.sendPacket(ServerboundPongPacket(0), ServerboundPongPacket)
|
|
||||||
}
|
|
||||||
|
|
||||||
0x01 -> println("configuration state disconnected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handlePlayPackets(packetId: Int, packetBuffer: _Buffer, output: _WriteChannel) {
|
|
||||||
try {
|
|
||||||
when (packetId) {
|
|
||||||
0x2B -> println("[PLAY] Joined world")
|
|
||||||
|
|
||||||
0x2c -> {
|
|
||||||
val keepAliveId = packetBuffer.readLong()
|
|
||||||
output.sendPacket(ServerboundKeepAlivePlayPacket(id = keepAliveId), ServerboundKeepAlivePlayPacket)
|
|
||||||
println("[PLAY] reply keep alive packet $keepAliveId")
|
|
||||||
}
|
|
||||||
|
|
||||||
0x1D -> println("[PLAY] disconnected (ClientboundDisconnectPlayPacket)")
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("parsing 0x${packetId.toString(16).uppercase()} failed, skipped: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet
|
|
||||||
|
|
||||||
internal sealed interface PacketDirection {
|
|
||||||
interface ServerboundPacket : PacketDirection
|
|
||||||
interface ClientboundPacket : PacketDirection
|
|
||||||
interface AcrossPacket : PacketDirection
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data object AckFinishConfigurationPacket : MinecraftPacket,
|
|
||||||
PacketCodec<AckFinishConfigurationPacket>,
|
|
||||||
PacketDirection.ServerboundPacket {
|
|
||||||
override val packetId: Int = 0x03
|
|
||||||
|
|
||||||
override fun encode(buffer: _Buffer, value: AckFinishConfigurationPacket) {}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): AckFinishConfigurationPacket = AckFinishConfigurationPacket
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.chat.util.readMinimalTextNbt
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class ClientboundDisconnectConfigurationPacket(
|
|
||||||
val reason: String,
|
|
||||||
) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x02
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundDisconnectConfigurationPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundDisconnectConfigurationPacket {
|
|
||||||
val reasonText = buffer.readMinimalTextNbt()
|
|
||||||
return ClientboundDisconnectConfigurationPacket(reason = reasonText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class ClientboundPingPacket(val id: Int) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x51
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ClientboundPingPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundPingPacket) {
|
|
||||||
buffer.writeInt(value.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundPingPacket = ClientboundPingPacket(buffer.readInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.*
|
|
||||||
|
|
||||||
internal data class ClientboundSelectKnownPacksPacket(
|
|
||||||
val knownPacks: List<KnownPacks>,
|
|
||||||
) : MinecraftPacket, PacketDirection.AcrossPacket {
|
|
||||||
override val packetId: Int = 0x0e
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ClientboundSelectKnownPacksPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundSelectKnownPacksPacket) {
|
|
||||||
buffer.writeVarInt(value.knownPacks.size)
|
|
||||||
value.knownPacks.forEach { KnownPacks.encode(buffer, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundSelectKnownPacksPacket {
|
|
||||||
val packsCount = buffer.readVarInt()
|
|
||||||
val packs = List(packsCount) { KnownPacks.decode(buffer) }
|
|
||||||
return ClientboundSelectKnownPacksPacket(packs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data object FinishConfigurationPacket : MinecraftPacket,
|
|
||||||
PacketCodec<FinishConfigurationPacket>,
|
|
||||||
PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x03
|
|
||||||
|
|
||||||
override fun encode(buffer: _Buffer, value: FinishConfigurationPacket) {}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): FinishConfigurationPacket = FinishConfigurationPacket
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class KeepAlivePacket(val id: Long) : MinecraftPacket, PacketDirection.AcrossPacket {
|
|
||||||
override val packetId: Int = 0x04
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<KeepAlivePacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: KeepAlivePacket) {
|
|
||||||
buffer.writeLong(value.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): KeepAlivePacket {
|
|
||||||
val id = buffer.readLong()
|
|
||||||
return KeepAlivePacket(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class ServerboundPongPacket(val id: Int) : MinecraftPacket, PacketDirection.ServerboundPacket {
|
|
||||||
override val packetId: Int = 0x2D
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ServerboundPongPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ServerboundPongPacket) {
|
|
||||||
buffer.writeInt(value.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ServerboundPongPacket = ServerboundPongPacket(buffer.readInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.*
|
|
||||||
|
|
||||||
internal data class ServerboundSelectKnownPacksPacket(
|
|
||||||
val knownPacks: List<KnownPacks>,
|
|
||||||
) : MinecraftPacket, PacketDirection.AcrossPacket {
|
|
||||||
override val packetId: Int = 0x07
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ServerboundSelectKnownPacksPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ServerboundSelectKnownPacksPacket) {
|
|
||||||
buffer.writeVarInt(value.knownPacks.size)
|
|
||||||
value.knownPacks.forEach { KnownPacks.encode(buffer, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ServerboundSelectKnownPacksPacket {
|
|
||||||
val packsCount = buffer.readVarInt()
|
|
||||||
val packs = List(packsCount) { KnownPacks.decode(buffer) }
|
|
||||||
return ServerboundSelectKnownPacksPacket(packs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.handshake
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
import cn.rtast.libmc.common.writeMcString
|
|
||||||
import cn.rtast.libmc.common.writeVarInt
|
|
||||||
|
|
||||||
internal data class HandshakePacket(
|
|
||||||
val protocolVersion: Int,
|
|
||||||
val serverAddress: String,
|
|
||||||
val serverPort: UShort,
|
|
||||||
// 1 for Status, 2 for Login, 3 for Transfer
|
|
||||||
val intent: Int
|
|
||||||
) : MinecraftPacket, PacketDirection.ServerboundPacket {
|
|
||||||
override val packetId: Int = 0x00
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<HandshakePacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: HandshakePacket) {
|
|
||||||
buffer.writeVarInt(value.protocolVersion)
|
|
||||||
buffer.writeMcString(value.serverAddress)
|
|
||||||
buffer.writeShort(value.serverPort.toShort())
|
|
||||||
buffer.writeVarInt(value.intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): HandshakePacket = throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.login
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
import cn.rtast.libmc.common.readMcString
|
|
||||||
|
|
||||||
internal data class ClientboundDisconnectLoginPacket(
|
|
||||||
val reason: String,
|
|
||||||
) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x00
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ClientboundDisconnectLoginPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundDisconnectLoginPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundDisconnectLoginPacket {
|
|
||||||
val reasonJson = buffer.readMcString()
|
|
||||||
return ClientboundDisconnectLoginPacket(reason = reasonJson)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.login
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class LoginAcknowledgedPacket(
|
|
||||||
override val packetId: Int = 0x03,
|
|
||||||
) : MinecraftPacket, PacketDirection.ServerboundPacket {
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<LoginAcknowledgedPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: LoginAcknowledgedPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): LoginAcknowledgedPacket = throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.login
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
import cn.rtast.libmc.common.writeMcString
|
|
||||||
import cn.rtast.libmc.common.writeUuid
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
internal data class LoginStartPacket(
|
|
||||||
val username: String,
|
|
||||||
val playerUuid: Uuid,
|
|
||||||
) : MinecraftPacket, PacketDirection.ServerboundPacket {
|
|
||||||
override val packetId: Int = 0x00
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<LoginStartPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: LoginStartPacket) {
|
|
||||||
buffer.writeMcString(value.username)
|
|
||||||
buffer.writeUuid(value.playerUuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): LoginStartPacket = throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.login
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.chat.profile.GameProfile
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
import cn.rtast.libmc.common.readUuid
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
internal data class LoginSuccessPacket(
|
|
||||||
val gameProfile: GameProfile,
|
|
||||||
val sessionId: Uuid,
|
|
||||||
) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x02
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<LoginSuccessPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: LoginSuccessPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): LoginSuccessPacket {
|
|
||||||
val gameProfile = GameProfile.decode(buffer)
|
|
||||||
val sessionId = buffer.readUuid()
|
|
||||||
return LoginSuccessPacket(gameProfile, sessionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.play
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.chat.util.readMinimalTextNbt
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class ClientboundDisconnectPlayPacket(
|
|
||||||
val reason: String,
|
|
||||||
) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x28
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ClientboundDisconnectPlayPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundDisconnectPlayPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundDisconnectPlayPacket {
|
|
||||||
val reasonText = buffer.readMinimalTextNbt()
|
|
||||||
return ClientboundDisconnectPlayPacket(reason = reasonText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.play
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class ClientboundKeepAlivePlayPacket(val id: Long) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
|
||||||
override val packetId: Int = 0x33
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ClientboundKeepAlivePlayPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundKeepAlivePlayPacket) {
|
|
||||||
buffer.writeLong(value.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundKeepAlivePlayPacket =
|
|
||||||
ClientboundKeepAlivePlayPacket(buffer.readLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.play
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.*
|
|
||||||
import kotlin.time.Clock
|
|
||||||
|
|
||||||
internal data class ServerboundChatMessagePacket(
|
|
||||||
val message: String,
|
|
||||||
val timestamp: Long = Clock.System.now().toEpochMilliseconds(),
|
|
||||||
val salt: Long = 0L,
|
|
||||||
) : MinecraftPacket, PacketDirection.ServerboundPacket {
|
|
||||||
override val packetId: Int = 0x09
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ServerboundChatMessagePacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ServerboundChatMessagePacket) {
|
|
||||||
buffer.writeMcString(value.message)
|
|
||||||
buffer.writeLong(value.timestamp)
|
|
||||||
buffer.writeLong(value.salt)
|
|
||||||
buffer.writeBoolean(false) // has signature
|
|
||||||
buffer.writeVarInt(0) // message count
|
|
||||||
buffer.writeBytes(byteArrayOf(0, 0, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ServerboundChatMessagePacket = throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.play
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
|
||||||
|
|
||||||
internal data class ServerboundKeepAlivePlayPacket(val id: Long) : MinecraftPacket, PacketDirection.ServerboundPacket {
|
|
||||||
override val packetId: Int = 0x1C
|
|
||||||
|
|
||||||
companion object Codec : PacketCodec<ServerboundKeepAlivePlayPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ServerboundKeepAlivePlayPacket) {
|
|
||||||
buffer.writeLong(value.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ServerboundKeepAlivePlayPacket =
|
|
||||||
ServerboundKeepAlivePlayPacket(buffer.readLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.protocol
|
|
||||||
|
|
||||||
internal object HandshakeIntent {
|
|
||||||
const val STATUS = 1
|
|
||||||
const val LOGIN = 2
|
|
||||||
const val TRANSFER = 3
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.util
|
|
||||||
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
|
|
||||||
public fun generateOfflineUuid(username: String): Uuid =
|
|
||||||
Uuid.fromByteArray("OfflinePlayer:$username".encodeToByteArray())
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package test
|
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.MinecraftChatClient
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
class TestChatClient {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test chat client`() = runTest {
|
|
||||||
val cli = MinecraftChatClient("127.0.0.1", 25565, "RTAkland", Uuid.parse("0dc6a9e9-a6df-3f3e-ae07-e6dbdf74b294"))
|
|
||||||
cli.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ package cn.rtast.libmc.common
|
|||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _Buffer {
|
public expect class BytesBuffer {
|
||||||
public constructor()
|
public constructor()
|
||||||
public constructor(bytes: ByteArray)
|
public constructor(bytes: ByteArray)
|
||||||
|
|
||||||
@@ -35,29 +35,27 @@ public expect class _Buffer {
|
|||||||
public val remaining: Long
|
public val remaining: Long
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun ByteArray.wrap(): _Buffer = _Buffer(this)
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
public inline fun ByteArray.wrap(): BytesBuffer = BytesBuffer(this)
|
||||||
|
|
||||||
public fun _Buffer.writeUuid(uuid: Uuid): Unit = uuid.toLongs { mostSignificantBits, leastSignificantBits ->
|
public fun BytesBuffer.writeUuid(uuid: Uuid): Unit = uuid.toLongs { mostSignificantBits, leastSignificantBits ->
|
||||||
this.writeLong(mostSignificantBits)
|
this.writeLong(mostSignificantBits)
|
||||||
this.writeLong(leastSignificantBits)
|
this.writeLong(leastSignificantBits)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun _Buffer.readUuid(): Uuid {
|
public fun BytesBuffer.readUuid(): Uuid {
|
||||||
val most = this.readLong()
|
val most = this.readLong()
|
||||||
val least = this.readLong()
|
val least = this.readLong()
|
||||||
return Uuid.fromLongs(most, least)
|
return Uuid.fromLongs(most, least)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun _Buffer.writeVarInt(value: Int): Unit = VarIntCodec.encode(this, value)
|
public fun BytesBuffer.writeVarInt(value: Int): Unit = VarIntCodec.encode(this, value)
|
||||||
public fun _Buffer.readVarInt(): Int = VarIntCodec.decode(this)
|
public fun BytesBuffer.readVarInt(): Int = VarIntCodec.decode(this)
|
||||||
|
|
||||||
public fun _Buffer.writeMcString(value: String): Unit = McStringCodec.encode(this, value)
|
public fun BytesBuffer.writeMcString(value: String): Unit = McStringCodec.encode(this, value)
|
||||||
public fun _Buffer.readMcString(): String = McStringCodec.decode(this)
|
public fun BytesBuffer.readMcString(): String = McStringCodec.decode(this)
|
||||||
|
|
||||||
public fun _ReadChannel.readPacketFrame(): _Buffer {
|
public fun ReadChannel.readPacketFrame(): BytesBuffer {
|
||||||
val length = this.readVarInt()
|
val length = this.readVarInt()
|
||||||
val frameBytes = this.readBytes(length)
|
return this.readBytes(length).wrap()
|
||||||
return _Buffer().apply {
|
|
||||||
writeBytes(frameBytes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
package cn.rtast.libmc.common
|
package cn.rtast.libmc.common
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _ReadChannel {
|
public expect class ReadChannel {
|
||||||
public fun readByte(): Byte
|
public fun readByte(): Byte
|
||||||
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
|
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
|
||||||
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
|
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
|
||||||
@@ -18,12 +18,12 @@ public expect class _ReadChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _WriteChannel {
|
public expect class WriteChannel {
|
||||||
public fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
|
public fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
|
||||||
public fun flush()
|
public fun flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun _ReadChannel.readVarInt(): Int {
|
public fun ReadChannel.readVarInt(): Int {
|
||||||
var numRead = 0
|
var numRead = 0
|
||||||
var result = 0
|
var result = 0
|
||||||
var read: Byte
|
var read: Byte
|
||||||
|
|||||||
@@ -8,17 +8,17 @@
|
|||||||
package cn.rtast.libmc.common
|
package cn.rtast.libmc.common
|
||||||
|
|
||||||
public interface Encoder<in T> {
|
public interface Encoder<in T> {
|
||||||
public fun encode(buffer: _Buffer, value: T)
|
public fun encode(buffer: BytesBuffer, value: T)
|
||||||
|
|
||||||
public fun encodeToByteArray(value: T): ByteArray {
|
public fun encodeToByteArray(value: T): ByteArray {
|
||||||
val buf = _Buffer()
|
val buf = BytesBuffer()
|
||||||
encode(buf, value)
|
encode(buf, value)
|
||||||
return buf.toByteArray()
|
return buf.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Decoder<out T> {
|
public interface Decoder<out T> {
|
||||||
public fun decode(buffer: _Buffer): T
|
public fun decode(buffer: BytesBuffer): T
|
||||||
|
|
||||||
public fun decodeFromByteArray(bytes: ByteArray): T = decode(bytes.wrap())
|
public fun decodeFromByteArray(bytes: ByteArray): T = decode(bytes.wrap())
|
||||||
}
|
}
|
||||||
@@ -26,12 +26,12 @@ public interface Decoder<out T> {
|
|||||||
public interface PacketCodec<T> : Encoder<T>, Decoder<T>
|
public interface PacketCodec<T> : Encoder<T>, Decoder<T>
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
public inline fun <T> _Buffer.write(value: T, encoder: Encoder<T>): Unit = encoder.encode(this, value)
|
public inline fun <T> BytesBuffer.write(value: T, encoder: Encoder<T>): Unit = encoder.encode(this, value)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
public inline fun <T> _Buffer.read(decoder: Decoder<T>): T = decoder.decode(this)
|
public inline fun <T> BytesBuffer.read(decoder: Decoder<T>): T = decoder.decode(this)
|
||||||
|
|
||||||
public fun _Buffer.writeBuffer(source: _Buffer, length: Long = source.remaining) {
|
public fun BytesBuffer.writeBuffer(source: BytesBuffer, length: Long = source.remaining) {
|
||||||
if (length <= 0) return
|
if (length <= 0) return
|
||||||
val bytes = source.readBytes(length.toInt())
|
val bytes = source.readBytes(length.toInt())
|
||||||
this.writeBytes(bytes)
|
this.writeBytes(bytes)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
package cn.rtast.libmc.common
|
package cn.rtast.libmc.common
|
||||||
|
|
||||||
public object VarIntCodec : PacketCodec<Int> {
|
public object VarIntCodec : PacketCodec<Int> {
|
||||||
override fun encode(buffer: _Buffer, value: Int) {
|
override fun encode(buffer: BytesBuffer, value: Int) {
|
||||||
var v = value
|
var v = value
|
||||||
while (true) {
|
while (true) {
|
||||||
if ((v and 0x7F.inv()) == 0) {
|
if ((v and 0x7F.inv()) == 0) {
|
||||||
@@ -19,7 +19,7 @@ public object VarIntCodec : PacketCodec<Int> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): Int {
|
override fun decode(buffer: BytesBuffer): Int {
|
||||||
var numRead = 0
|
var numRead = 0
|
||||||
var result = 0
|
var result = 0
|
||||||
var read: Byte
|
var read: Byte
|
||||||
@@ -35,13 +35,13 @@ public object VarIntCodec : PacketCodec<Int> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public object McStringCodec : PacketCodec<String> {
|
public object McStringCodec : PacketCodec<String> {
|
||||||
override fun encode(buffer: _Buffer, value: String) {
|
override fun encode(buffer: BytesBuffer, value: String) {
|
||||||
val bytes = value.encodeToByteArray()
|
val bytes = value.encodeToByteArray()
|
||||||
VarIntCodec.encode(buffer, bytes.size)
|
VarIntCodec.encode(buffer, bytes.size)
|
||||||
buffer.writeBytes(bytes)
|
buffer.writeBytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): String {
|
override fun decode(buffer: BytesBuffer): String {
|
||||||
val length = VarIntCodec.decode(buffer)
|
val length = VarIntCodec.decode(buffer)
|
||||||
val bytes = buffer.readBytes(length)
|
val bytes = buffer.readBytes(length)
|
||||||
return bytes.decodeToString()
|
return bytes.decodeToString()
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.common
|
|
||||||
|
|
||||||
public interface MinecraftPacket {
|
|
||||||
public val packetId: Int
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.common.packet
|
||||||
|
|
||||||
|
public interface MinecraftPacket
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.common.packet
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
public class PacketRegistry {
|
||||||
|
private val idToCodec = mutableMapOf<Int, PacketCodec<out MinecraftPacket>>()
|
||||||
|
private val classToInfo = mutableMapOf<KClass<out MinecraftPacket>, RegisteredPacket<*>>()
|
||||||
|
|
||||||
|
private data class RegisteredPacket<P : MinecraftPacket>(
|
||||||
|
val id: Int, val codec: PacketCodec<P>,
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun <T : MinecraftPacket> register(id: Int, kClass: KClass<T>, codec: PacketCodec<T>) {
|
||||||
|
idToCodec[id] = codec
|
||||||
|
classToInfo[kClass] = RegisteredPacket(id, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun <reified T : MinecraftPacket> register(id: Int, codec: PacketCodec<T>) {
|
||||||
|
register(id, T::class, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun decodePacket(packetId: Int, buffer: BytesBuffer): MinecraftPacket {
|
||||||
|
val codec = idToCodec[packetId]
|
||||||
|
if (codec != null) return codec.decode(buffer) else {
|
||||||
|
// println("Ignored unknown packet 0x${packetId.toString(16).uppercase()}")
|
||||||
|
return UnknownPacket(packetId, buffer.readBytes(buffer.remaining.toInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : MinecraftPacket> encodePacket(buffer: BytesBuffer, packet: T) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val info = requireNotNull(classToInfo[packet::class]) {
|
||||||
|
"Unregistered Packet ${packet::class.simpleName}"
|
||||||
|
} as RegisteredPacket<T>
|
||||||
|
buffer.writeVarInt(info.id)
|
||||||
|
info.codec.encode(buffer, packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.common.packet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reserved packet
|
||||||
|
*/
|
||||||
|
public data class UnknownPacket(val packetId: Int, val data: ByteArray): MinecraftPacket {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as UnknownPacket
|
||||||
|
|
||||||
|
if (packetId != other.packetId) return false
|
||||||
|
if (!data.contentEquals(other.data)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = packetId
|
||||||
|
result = 31 * result + data.contentHashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2026 RTAkland
|
|
||||||
* Author: RTAkland
|
|
||||||
* Date: 2026/9/4
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.common
|
|
||||||
|
|
||||||
public fun <T : MinecraftPacket> _WriteChannel.sendPacket(packet: T, codec: PacketCodec<T>) {
|
|
||||||
val bodyBuffer = _Buffer()
|
|
||||||
bodyBuffer.write(packet.packetId, VarIntCodec)
|
|
||||||
codec.encode(bodyBuffer, packet)
|
|
||||||
val frameBuffer = _Buffer()
|
|
||||||
frameBuffer.write(bodyBuffer.size, VarIntCodec)
|
|
||||||
frameBuffer.writeBuffer(bodyBuffer)
|
|
||||||
val bytes = frameBuffer.toByteArray()
|
|
||||||
this.writeFully(bytes, 0, bytes.size)
|
|
||||||
this.flush()
|
|
||||||
}
|
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
package cn.rtast.libmc.common
|
package cn.rtast.libmc.common
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _Socket public constructor(host: String, port: Int, context: LibMCContext) {
|
public expect class Socket public constructor(host: String, port: Int, context: LibMCContext) {
|
||||||
public fun openReadChannel(): _ReadChannel
|
public fun openReadChannel(): ReadChannel
|
||||||
public fun openWriteChannel(): _WriteChannel
|
public fun openWriteChannel(): WriteChannel
|
||||||
public fun close()
|
public fun close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ package cn.rtast.libmc.common
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _Buffer {
|
public actual class BytesBuffer {
|
||||||
private val outStream = ByteArrayOutputStream()
|
private val outStream = ByteArrayOutputStream()
|
||||||
private var readBuffer: ByteArray? = null
|
private var readBuffer: ByteArray? = null
|
||||||
private var readOffset = 0
|
private var readOffset = 0
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.io.InputStream
|
|||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _ReadChannel(private val _inputStream: InputStream) {
|
public actual class ReadChannel(private val _inputStream: InputStream) {
|
||||||
|
|
||||||
public actual fun readByte(): Byte = _inputStream.read().toByte()
|
public actual fun readByte(): Byte = _inputStream.read().toByte()
|
||||||
public actual fun readBytes(length: Int): ByteArray = _inputStream.readNBytes(length)
|
public actual fun readBytes(length: Int): ByteArray = _inputStream.readNBytes(length)
|
||||||
@@ -33,7 +33,7 @@ public actual class _ReadChannel(private val _inputStream: InputStream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _WriteChannel {
|
public actual class WriteChannel {
|
||||||
private val _outputStream: OutputStream
|
private val _outputStream: OutputStream
|
||||||
|
|
||||||
public constructor(outputStream: OutputStream) {
|
public constructor(outputStream: OutputStream) {
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import java.net.InetSocketAddress
|
|||||||
import java.net.Socket as JvmSocket
|
import java.net.Socket as JvmSocket
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
|
public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
|
||||||
private val socket = JvmSocket(host, port)
|
private val socket = JvmSocket(host, port)
|
||||||
|
|
||||||
public actual fun openReadChannel(): _ReadChannel = _ReadChannel(socket.getInputStream())
|
public actual fun openReadChannel(): ReadChannel = ReadChannel(socket.getInputStream())
|
||||||
public actual fun openWriteChannel(): _WriteChannel = _WriteChannel(socket.getOutputStream())
|
public actual fun openWriteChannel(): WriteChannel = WriteChannel(socket.getOutputStream())
|
||||||
public actual fun close(): Unit = socket.close()
|
public actual fun close(): Unit = socket.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import kotlinx.io.Buffer
|
|||||||
import kotlinx.io.readByteArray
|
import kotlinx.io.readByteArray
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _Buffer {
|
public actual class BytesBuffer {
|
||||||
private val _delegateBuf: Buffer
|
private val _delegateBuf: Buffer
|
||||||
|
|
||||||
public actual constructor() {
|
public actual constructor() {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import io.ktor.utils.io.bits.reverseByteOrder
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _ReadChannel(private val _readChannel: ByteReadChannel) {
|
public actual class ReadChannel(private val _readChannel: ByteReadChannel) {
|
||||||
|
|
||||||
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
|
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
|
||||||
public actual fun readBytes(length: Int): ByteArray = runBlocking { _readChannel.readByteArray(length) }
|
public actual fun readBytes(length: Int): ByteArray = runBlocking { _readChannel.readByteArray(length) }
|
||||||
@@ -35,7 +35,7 @@ public actual class _ReadChannel(private val _readChannel: ByteReadChannel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _WriteChannel {
|
public actual class WriteChannel {
|
||||||
private val _writeChannel: ByteWriteChannel
|
private val _writeChannel: ByteWriteChannel
|
||||||
|
|
||||||
public constructor(writeChannel: ByteWriteChannel) {
|
public constructor(writeChannel: ByteWriteChannel) {
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.io.readByteArray
|
import kotlinx.io.readByteArray
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
|
public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
|
||||||
private val ctx = context
|
private val ctx = context
|
||||||
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
|
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
|
||||||
|
|
||||||
public actual fun openReadChannel(): _ReadChannel = _ReadChannel(socket.openReadChannel())
|
public actual fun openReadChannel(): ReadChannel = ReadChannel(socket.openReadChannel())
|
||||||
public actual fun openWriteChannel(): _WriteChannel =
|
public actual fun openWriteChannel(): WriteChannel =
|
||||||
_WriteChannel(socket.openWriteChannel(autoFlush = true))
|
WriteChannel(socket.openWriteChannel(autoFlush = true))
|
||||||
|
|
||||||
public actual fun close() {
|
public actual fun close() {
|
||||||
socket.close()
|
socket.close()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
package cn.rtast.libmc.mcping.bedrock
|
package cn.rtast.libmc.mcping.bedrock
|
||||||
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
private val RAKNET_MAGIC = byteArrayOf(
|
private val RAKNET_MAGIC = byteArrayOf(
|
||||||
@@ -33,14 +33,14 @@ internal data class BedrockRequestPacket(
|
|||||||
override val packetId: Byte = 0x01
|
override val packetId: Byte = 0x01
|
||||||
|
|
||||||
companion object Codec : PacketCodec<BedrockRequestPacket> {
|
companion object Codec : PacketCodec<BedrockRequestPacket> {
|
||||||
override fun encode(buffer: _Buffer, value: BedrockRequestPacket) {
|
override fun encode(buffer: BytesBuffer, value: BedrockRequestPacket) {
|
||||||
buffer.writeByte(value.packetId)
|
buffer.writeByte(value.packetId)
|
||||||
buffer.writeLong(value.time)
|
buffer.writeLong(value.time)
|
||||||
buffer.writeBytes(value.magic)
|
buffer.writeBytes(value.magic)
|
||||||
buffer.writeLong(value.guid)
|
buffer.writeLong(value.guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): BedrockRequestPacket = throw UnsupportedOperationException()
|
override fun decode(buffer: BytesBuffer): BedrockRequestPacket = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -72,8 +72,8 @@ internal data class BedrockResponsePacket(
|
|||||||
) : MinecraftBedrockPacket {
|
) : MinecraftBedrockPacket {
|
||||||
|
|
||||||
companion object Codec : PacketCodec<BedrockResponsePacket> {
|
companion object Codec : PacketCodec<BedrockResponsePacket> {
|
||||||
override fun encode(buffer: _Buffer, value: BedrockResponsePacket) = throw UnsupportedOperationException()
|
override fun encode(buffer: BytesBuffer, value: BedrockResponsePacket) = throw UnsupportedOperationException()
|
||||||
override fun decode(buffer: _Buffer): BedrockResponsePacket {
|
override fun decode(buffer: BytesBuffer): BedrockResponsePacket {
|
||||||
val packetId = buffer.readByte()
|
val packetId = buffer.readByte()
|
||||||
if (packetId != 0x1C.toByte()) throw IllegalStateException("Expected pong id 0x1C, got $packetId")
|
if (packetId != 0x1C.toByte()) throw IllegalStateException("Expected pong id 0x1C, got $packetId")
|
||||||
val time = buffer.readLong()
|
val time = buffer.readLong()
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
package cn.rtast.libmc.mcping.bedrock
|
package cn.rtast.libmc.mcping.bedrock
|
||||||
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import cn.rtast.libmc.common._UdpSocket
|
import cn.rtast.libmc.common._UdpSocket
|
||||||
|
|
||||||
|
|
||||||
internal fun <T : MinecraftBedrockPacket> _UdpSocket.sendPacket(packet: T, codec: PacketCodec<T>): ByteArray {
|
internal fun <T : MinecraftBedrockPacket> _UdpSocket.sendPacket(packet: T, codec: PacketCodec<T>): ByteArray {
|
||||||
val buf = _Buffer()
|
val buf = BytesBuffer()
|
||||||
codec.encode(buf, packet)
|
codec.encode(buf, packet)
|
||||||
return sendAndReceive(buf.toByteArray())
|
return sendAndReceive(buf.toByteArray())
|
||||||
}
|
}
|
||||||
@@ -7,47 +7,45 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.mcping.java
|
package cn.rtast.libmc.mcping.java
|
||||||
|
|
||||||
import cn.rtast.libmc.common.*
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.McStringCodec
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.VarIntCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Handshake
|
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Handshake
|
||||||
public data class HandshakePacket(
|
internal data class HandshakePacket(
|
||||||
val protocolVersion: Int,
|
val protocolVersion: Int,
|
||||||
val serverAddress: String,
|
val serverAddress: String,
|
||||||
val serverPort: UShort,
|
val serverPort: UShort,
|
||||||
// 1 -> Status, 2 -> Login
|
// 1 -> Status, 2 -> Login
|
||||||
val nextState: Int,
|
val nextState: Int,
|
||||||
) : MinecraftPacket {
|
) : MinecraftPacket {
|
||||||
override val packetId: Int = 0x00
|
companion object Codec : PacketCodec<HandshakePacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: HandshakePacket) {
|
||||||
public companion object Codec : PacketCodec<HandshakePacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: HandshakePacket) {
|
|
||||||
VarIntCodec.encode(buffer, value.protocolVersion)
|
VarIntCodec.encode(buffer, value.protocolVersion)
|
||||||
McStringCodec.encode(buffer, value.serverAddress)
|
McStringCodec.encode(buffer, value.serverAddress)
|
||||||
buffer.writeShort(value.serverPort.toShort())
|
buffer.writeShort(value.serverPort.toShort())
|
||||||
VarIntCodec.encode(buffer, value.nextState)
|
VarIntCodec.encode(buffer, value.nextState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): HandshakePacket = throw UnsupportedOperationException()
|
override fun decode(buffer: BytesBuffer): HandshakePacket = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Status
|
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Status
|
||||||
public data object StatusRequestPacket : MinecraftPacket, PacketCodec<StatusRequestPacket> {
|
internal data object StatusRequestPacket : MinecraftPacket, PacketCodec<StatusRequestPacket> {
|
||||||
override val packetId: Int = 0x00
|
override fun encode(buffer: BytesBuffer, value: StatusRequestPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): StatusRequestPacket = throw UnsupportedOperationException()
|
||||||
override fun encode(buffer: _Buffer, value: StatusRequestPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): StatusRequestPacket = throw UnsupportedOperationException()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public data class PingPacket(val currentTime: Long) : MinecraftPacket {
|
internal data class PingPacket(val currentTime: Long) : MinecraftPacket {
|
||||||
override val packetId: Int = 0x01
|
companion object : PacketCodec<PingPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: PingPacket) {
|
||||||
public companion object : PacketCodec<PingPacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: PingPacket) {
|
|
||||||
buffer.writeLong(value.currentTime)
|
buffer.writeLong(value.currentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): PingPacket {
|
override fun decode(buffer: BytesBuffer): PingPacket {
|
||||||
return PingPacket(buffer.readLong())
|
return PingPacket(buffer.readLong())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ package cn.rtast.libmc.mcping.java
|
|||||||
|
|
||||||
import cn.rtast.libmc.common.*
|
import cn.rtast.libmc.common.*
|
||||||
import cn.rtast.libmc.mcping.PingResponse
|
import cn.rtast.libmc.mcping.PingResponse
|
||||||
|
import cn.rtast.libmc.mcping.sendPacket
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
internal fun pingJavaServer(host: String, port: Int, context: LibMCContext): PingResponse {
|
internal fun pingJavaServer(host: String, port: Int, context: LibMCContext): PingResponse {
|
||||||
val socket = _Socket(host, port, context)
|
val socket = Socket(host, port, context)
|
||||||
val receiveChannel = socket.openReadChannel()
|
val receiveChannel = socket.openReadChannel()
|
||||||
val sendChannel = socket.openWriteChannel()
|
val sendChannel = socket.openWriteChannel()
|
||||||
|
|
||||||
@@ -23,8 +24,8 @@ internal fun pingJavaServer(host: String, port: Int, context: LibMCContext): Pin
|
|||||||
serverPort = port.toUShort(),
|
serverPort = port.toUShort(),
|
||||||
nextState = 1
|
nextState = 1
|
||||||
)
|
)
|
||||||
sendChannel.sendPacket(handshakePacket, HandshakePacket)
|
sendChannel.sendPacket(handshakePacket, 0x00, HandshakePacket)
|
||||||
sendChannel.sendPacket(StatusRequestPacket, StatusRequestPacket)
|
sendChannel.sendPacket(StatusRequestPacket, 0x00, StatusRequestPacket)
|
||||||
|
|
||||||
val statusFrameBuffer = receiveChannel.readPacketFrame()
|
val statusFrameBuffer = receiveChannel.readPacketFrame()
|
||||||
val statusPacketId = VarIntCodec.decode(statusFrameBuffer)
|
val statusPacketId = VarIntCodec.decode(statusFrameBuffer)
|
||||||
@@ -35,7 +36,7 @@ internal fun pingJavaServer(host: String, port: Int, context: LibMCContext): Pin
|
|||||||
|
|
||||||
val sendTime = Clock.System.now().toEpochMilliseconds()
|
val sendTime = Clock.System.now().toEpochMilliseconds()
|
||||||
val pingPacket = PingPacket(sendTime)
|
val pingPacket = PingPacket(sendTime)
|
||||||
sendChannel.sendPacket(pingPacket, PingPacket)
|
sendChannel.sendPacket(pingPacket, 0x01, PingPacket)
|
||||||
|
|
||||||
val pongFrameBuffer = receiveChannel.readPacketFrame()
|
val pongFrameBuffer = receiveChannel.readPacketFrame()
|
||||||
val pongPacketId = VarIntCodec.decode(pongFrameBuffer)
|
val pongPacketId = VarIntCodec.decode(pongFrameBuffer)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.mcping
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.*
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public fun <T : MinecraftPacket> WriteChannel.sendPacket(packet: T, packetId: Int, codec: PacketCodec<T>) {
|
||||||
|
val bodyBuffer = BytesBuffer()
|
||||||
|
bodyBuffer.write(packetId, VarIntCodec)
|
||||||
|
codec.encode(bodyBuffer, packet)
|
||||||
|
val frameBuffer = BytesBuffer()
|
||||||
|
frameBuffer.write(bodyBuffer.size, VarIntCodec)
|
||||||
|
frameBuffer.writeBuffer(bodyBuffer)
|
||||||
|
val bytes = frameBuffer.toByteArray()
|
||||||
|
this.writeFully(bytes, 0, bytes.size)
|
||||||
|
this.flush()
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -0,0 +1,146 @@
|
|||||||
|
///*
|
||||||
|
// * Copyright © 2026 RTAkland
|
||||||
|
// * Author: RTAkland
|
||||||
|
// * Date: 2026/9/4
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//package cn.rtast.libmc.protocol
|
||||||
|
//
|
||||||
|
//import cn.rtast.libmc.protocol.packet.configuration.ServerboundAckFinishConfigurationPacket
|
||||||
|
//import cn.rtast.libmc.protocol.packet.configuration.ServerboundPongPacket
|
||||||
|
//import cn.rtast.libmc.protocol.packet.configuration.ServerboundSelectKnownPacksPacket
|
||||||
|
//import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket
|
||||||
|
//import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
|
||||||
|
//import cn.rtast.libmc.protocol.packet.login.ServerboundLoginStartPacket
|
||||||
|
//import cn.rtast.libmc.protocol.packet.play.ServerboundKeepAlivePlayPacket
|
||||||
|
//import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent
|
||||||
|
//import cn.rtast.libmc.protocol.protocol.state.ProtocolState
|
||||||
|
//import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
||||||
|
//import cn.rtast.libmc.common.*
|
||||||
|
//import kotlinx.coroutines.Dispatchers
|
||||||
|
//import kotlinx.coroutines.coroutineScope
|
||||||
|
//import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
//import kotlinx.coroutines.isActive
|
||||||
|
//import kotlinx.coroutines.launch
|
||||||
|
//import kotlin.uuid.Uuid
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//public class MinecraftChatClient(
|
||||||
|
// private val host: String,
|
||||||
|
// private val port: Int,
|
||||||
|
// private val username: String,
|
||||||
|
// private val uuid: Uuid = generateOfflineUuid(username),
|
||||||
|
// private val context: LibMCContext = LibMCContext(),
|
||||||
|
//) {
|
||||||
|
// private var state = ProtocolState.HANDSHAKE
|
||||||
|
//
|
||||||
|
// public suspend fun start(): Unit = coroutineScope {
|
||||||
|
// val socket = Socket(host, port, context)
|
||||||
|
// val input = socket.openReadChannel()
|
||||||
|
// val output = socket.openWriteChannel()
|
||||||
|
//
|
||||||
|
// executeInitHandshake(output)
|
||||||
|
//
|
||||||
|
// val readerJob = launch(Dispatchers.Default) {
|
||||||
|
// handleIncomingPackets(input, output)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// readerJob.join()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun executeInitHandshake(output: WriteChannel) {
|
||||||
|
// val handshakePacket = ServerboundHandshakePacket(776, host, port.toUShort(), HandshakeIntent.LOGIN)
|
||||||
|
// output.sendPacket(handshakePacket, ServerboundHandshakePacket)
|
||||||
|
// state = ProtocolState.LOGIN
|
||||||
|
//
|
||||||
|
// val loginStartPacket = ServerboundLoginStartPacket(username, uuid)
|
||||||
|
// output.sendPacket(loginStartPacket, ServerboundLoginStartPacket)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private suspend fun handleIncomingPackets(input: ReadChannel, output: WriteChannel) {
|
||||||
|
// try {
|
||||||
|
// while (currentCoroutineContext().isActive) {
|
||||||
|
// val packetLength = input.readVarInt()
|
||||||
|
// if (packetLength <= 0) continue
|
||||||
|
//
|
||||||
|
// val packetBytes = ByteArray(packetLength)
|
||||||
|
// input.readFully(packetBytes, 0, packetLength)
|
||||||
|
//
|
||||||
|
// val buffer = BytesBuffer(packetBytes)
|
||||||
|
// val packetId = buffer.readVarInt()
|
||||||
|
// println("received -> State: $state | ID: 0x${packetId.toString(16).uppercase()} | Length: $packetLength")
|
||||||
|
// try {
|
||||||
|
// when (state) {
|
||||||
|
// ProtocolState.LOGIN -> handleLoginPackets(packetId, output)
|
||||||
|
// ProtocolState.CONFIGURATION -> handleConfigurationPackets(packetId, buffer, output)
|
||||||
|
// ProtocolState.PLAY -> handlePlayPackets(packetId, buffer, output)
|
||||||
|
// else -> {}
|
||||||
|
// }
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// println("parsing 0x${packetId.toString(16).uppercase()} Payload failed: ${e.message}")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// e.printStackTrace()
|
||||||
|
// println("disconnecting: ${e.message}")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun handleLoginPackets(packetId: Int, output: WriteChannel) {
|
||||||
|
// when (packetId) {
|
||||||
|
// 0x02 -> {
|
||||||
|
// output.sendPacket(ServerboundLoginAcknowledgedPacket(), ServerboundLoginAcknowledgedPacket)
|
||||||
|
// state = ProtocolState.CONFIGURATION
|
||||||
|
// println("[3/4] sent LoginAcknowledgedPacket -> switching to CONFIGURATION state")
|
||||||
|
//
|
||||||
|
// output.sendPacket(
|
||||||
|
// ServerboundSelectKnownPacksPacket(knownPacks = emptyList()),
|
||||||
|
// ServerboundSelectKnownPacksPacket
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 0x00 -> {
|
||||||
|
// println("login denied (ClientboundDisconnectLoginPacket)")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun handleConfigurationPackets(packetId: Int, packetBuffer: BytesBuffer, output: WriteChannel) {
|
||||||
|
// when (packetId) {
|
||||||
|
// 0x0E -> {
|
||||||
|
// println("received ClientboundSelectKnownPacksPacket")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 0x03 -> {
|
||||||
|
// output.sendPacket(ServerboundAckFinishConfigurationPacket, ServerboundAckFinishConfigurationPacket)
|
||||||
|
// state = ProtocolState.PLAY
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 0x05 -> {
|
||||||
|
// output.sendPacket(ServerboundPongPacket(0), ServerboundPongPacket)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 0x01 -> println("configuration state disconnected")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun handlePlayPackets(packetId: Int, packetBuffer: BytesBuffer, output: WriteChannel) {
|
||||||
|
// try {
|
||||||
|
// when (packetId) {
|
||||||
|
// 0x2B -> println("[PLAY] Joined world")
|
||||||
|
//
|
||||||
|
// 0x2c -> {
|
||||||
|
// val keepAliveId = packetBuffer.readLong()
|
||||||
|
// output.sendPacket(ServerboundKeepAlivePlayPacket(id = keepAliveId), ServerboundKeepAlivePlayPacket)
|
||||||
|
// println("[PLAY] reply keep alive packet $keepAliveId")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 0x1D -> println("[PLAY] disconnected (ClientboundDisconnectPlayPacket)")
|
||||||
|
// else -> {}
|
||||||
|
// }
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// println("parsing 0x${packetId.toString(16).uppercase()} failed, skipped: ${e.message}")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.chat
|
package cn.rtast.libmc.protocol.chat
|
||||||
|
|
||||||
public enum class ChatFilterType(public val id: Int) {
|
public enum class ChatFilterType(public val id: Int) {
|
||||||
PASS_THROUGH(0),
|
PASS_THROUGH(0),
|
||||||
+4
-4
@@ -5,10 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.chat
|
package cn.rtast.libmc.protocol.chat
|
||||||
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import cn.rtast.libmc.common.writeVarInt
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
|
||||||
public data class PreviousMessageEntry(
|
public data class PreviousMessageEntry(
|
||||||
@@ -16,7 +16,7 @@ public data class PreviousMessageEntry(
|
|||||||
val signature: ByteArray?,
|
val signature: ByteArray?,
|
||||||
) {
|
) {
|
||||||
public companion object Codec : PacketCodec<PreviousMessageEntry> {
|
public companion object Codec : PacketCodec<PreviousMessageEntry> {
|
||||||
override fun encode(buffer: _Buffer, value: PreviousMessageEntry) {
|
override fun encode(buffer: BytesBuffer, value: PreviousMessageEntry) {
|
||||||
buffer.writeVarInt(value.messageId)
|
buffer.writeVarInt(value.messageId)
|
||||||
if (value.messageId == 0) {
|
if (value.messageId == 0) {
|
||||||
val sig = requireNotNull(value.signature) { "signature must be present when messageId is 0" }
|
val sig = requireNotNull(value.signature) { "signature must be present when messageId is 0" }
|
||||||
@@ -25,7 +25,7 @@ public data class PreviousMessageEntry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): PreviousMessageEntry = throw UnsupportedOperationException() // TODO
|
override fun decode(buffer: BytesBuffer): PreviousMessageEntry = throw UnsupportedOperationException() // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.client
|
||||||
|
|
||||||
|
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
|
|
||||||
|
internal class ClientStateMachine {
|
||||||
|
@Volatile
|
||||||
|
var currentState: ProtocolState = ProtocolState.HANDSHAKE
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun transitionTo(newState: ProtocolState) {
|
||||||
|
println("Changing State $currentState to $newState")
|
||||||
|
currentState = newState
|
||||||
|
}
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.client
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.LibMCContext
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.packet.UnknownPacket
|
||||||
|
import cn.rtast.libmc.protocol.network.NetworkChannel
|
||||||
|
import cn.rtast.libmc.protocol.packet.configuration.*
|
||||||
|
import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ClientboundDisconnectLoginPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ClientboundLoginSuccessPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginStartPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.play.*
|
||||||
|
import cn.rtast.libmc.protocol.protocol.GameProtocols
|
||||||
|
import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent
|
||||||
|
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
|
||||||
|
import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
public class MinecraftClient(
|
||||||
|
private val host: String,
|
||||||
|
private val port: Int = 25565,
|
||||||
|
private val username: String,
|
||||||
|
private val uuid: Uuid = generateOfflineUuid(username),
|
||||||
|
private val context: LibMCContext = LibMCContext(),
|
||||||
|
) {
|
||||||
|
private val stateMachine = ClientStateMachine()
|
||||||
|
private val networkChannel = NetworkChannel(host, port, context, stateMachine)
|
||||||
|
private val listeners = mutableListOf<(MinecraftPacket) -> Unit>()
|
||||||
|
private var listenJob: Job? = null
|
||||||
|
|
||||||
|
public suspend fun connect(protocolVersion: Int = 776) {
|
||||||
|
networkChannel.connect()
|
||||||
|
startListening()
|
||||||
|
networkChannel.sendPacket(
|
||||||
|
ServerboundHandshakePacket(
|
||||||
|
protocolVersion, host,
|
||||||
|
port.toUShort(),
|
||||||
|
HandshakeIntent.LOGIN
|
||||||
|
)
|
||||||
|
)
|
||||||
|
stateMachine.transitionTo(ProtocolState.LOGIN)
|
||||||
|
networkChannel.sendPacket(ServerboundLoginStartPacket(username, uuid))
|
||||||
|
listenJob?.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startListening() {
|
||||||
|
listenJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
while (isActive) {
|
||||||
|
val packet = networkChannel.readNextPacket()
|
||||||
|
handleIncomingPackets(packet)
|
||||||
|
listeners.forEach { it.invoke(packet) }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
if (isActive) {
|
||||||
|
println("Network read loop exception: ${e.message}")
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleIncomingPackets(packet: MinecraftPacket) {
|
||||||
|
when (packet) {
|
||||||
|
is ClientboundLoginSuccessPacket -> {
|
||||||
|
networkChannel.sendPacket(ServerboundLoginAcknowledgedPacket)
|
||||||
|
stateMachine.transitionTo(ProtocolState.CONFIGURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundDisconnectLoginPacket -> {
|
||||||
|
println("Login denied: ${packet.reason}")
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundSelectKnownPacksPacket -> {
|
||||||
|
networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList())) // TODO empty resource packs list
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundPingPacket -> networkChannel.sendPacket(ServerboundPongPacket(packet.id))
|
||||||
|
|
||||||
|
is ClientboundKeepAliveConfigurationPacket -> {
|
||||||
|
networkChannel.sendPacket(ServerboundKeepAliveConfigurationPacket(packet.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundFinishConfigurationPacket -> {
|
||||||
|
networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket)
|
||||||
|
stateMachine.transitionTo(ProtocolState.PLAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundDisconnectConfigurationPacket -> {
|
||||||
|
println("Configuration disconnected: ${packet.reason}")
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundLoginPlayPacket -> {
|
||||||
|
println("Successfully joined world! Entity ID: ${packet.entityId}")
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundKeepAlivePlayPacket -> {
|
||||||
|
networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(id = packet.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundStartConfigurationPacket -> {
|
||||||
|
networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket)
|
||||||
|
stateMachine.transitionTo(ProtocolState.CONFIGURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClientboundDisconnectPlayPacket -> {
|
||||||
|
println("Disconnected from play session: ${packet.reason}")
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
// else -> println((packet as? UnknownPacket)?.data?.contentToString() ?: packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun onPacket(listener: (MinecraftPacket) -> Unit) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun close() {
|
||||||
|
listenJob?.cancel()
|
||||||
|
networkChannel.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.codec
|
||||||
|
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.network
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.*
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.protocol.client.ClientStateMachine
|
||||||
|
import cn.rtast.libmc.protocol.protocol.GameProtocols
|
||||||
|
|
||||||
|
internal class NetworkChannel(
|
||||||
|
private val host: String,
|
||||||
|
private val port: Int,
|
||||||
|
private val context: LibMCContext,
|
||||||
|
private val stateMachine: ClientStateMachine,
|
||||||
|
) {
|
||||||
|
private var socket: Socket? = null
|
||||||
|
private var readChannel: ReadChannel? = null
|
||||||
|
private var writeChannel: WriteChannel? = null
|
||||||
|
|
||||||
|
fun connect() {
|
||||||
|
val sk = Socket(host, port, context)
|
||||||
|
this.socket = sk
|
||||||
|
this.readChannel = sk.openReadChannel()
|
||||||
|
this.writeChannel = sk.openWriteChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readNextPacket(): MinecraftPacket {
|
||||||
|
val channel = requireNotNull(readChannel) { "ReadChannel not connected" }
|
||||||
|
val length = channel.readVarInt()
|
||||||
|
val buf = channel.readBytes(length).wrap()
|
||||||
|
val currentState = stateMachine.currentState
|
||||||
|
val packetId = buf.readVarInt()
|
||||||
|
return GameProtocols.clientboundGameProtocols.getRegistry(currentState).decodePacket(packetId, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendPacket(packet: MinecraftPacket) {
|
||||||
|
val channel = requireNotNull(writeChannel) { "WriteChannel not connected" }
|
||||||
|
val bodyBuffer = BytesBuffer()
|
||||||
|
GameProtocols.serverboundGameProtocols.getRegistry(stateMachine.currentState).encodePacket(bodyBuffer, packet)
|
||||||
|
val frameBuffer = BytesBuffer().apply {
|
||||||
|
writeVarInt(bodyBuffer.size)
|
||||||
|
writeBuffer(bodyBuffer)
|
||||||
|
}
|
||||||
|
channel.writeFully(frameBuffer.toByteArray())
|
||||||
|
channel.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
socket?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.Identifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
|
||||||
|
|
||||||
|
public data class ClientboundCookieRequestPacket(val key: Identifier) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundCookieRequestPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundCookieRequestPacket) {
|
||||||
|
buffer.writeIdentifier(value.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundCookieRequestPacket {
|
||||||
|
return ClientboundCookieRequestPacket(key = buffer.readIdentifier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.Identifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
|
||||||
|
|
||||||
|
public data class ClientboundCustomPayloadPacket(val channel: Identifier, val data: ByteArray) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundCustomPayloadPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundCustomPayloadPacket) {
|
||||||
|
buffer.writeIdentifier(value.channel)
|
||||||
|
buffer.writeBytes(value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundCustomPayloadPacket {
|
||||||
|
val channel = buffer.readIdentifier()
|
||||||
|
val data = buffer.readBytes(buffer.remaining.toInt())
|
||||||
|
return ClientboundCustomPayloadPacket(channel, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as ClientboundCustomPayloadPacket
|
||||||
|
|
||||||
|
if (channel != other.channel) return false
|
||||||
|
if (!data.contentEquals(other.data)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = channel.hashCode()
|
||||||
|
result = 31 * result + data.contentHashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.protocol.util.readMinimalTextNbt
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
|
||||||
|
public data class ClientboundDisconnectConfigurationPacket(val reason: String) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket {
|
||||||
|
val reasonText = buffer.readMinimalTextNbt()
|
||||||
|
return ClientboundDisconnectConfigurationPacket(reason = reasonText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data object ClientboundFinishConfigurationPacket : MinecraftPacket,
|
||||||
|
PacketCodec<ClientboundFinishConfigurationPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundFinishConfigurationPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundFinishConfigurationPacket {
|
||||||
|
return ClientboundFinishConfigurationPacket
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ClientboundKeepAliveConfigurationPacket(val id: Long) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundKeepAliveConfigurationPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAliveConfigurationPacket) {
|
||||||
|
buffer.writeLong(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundKeepAliveConfigurationPacket {
|
||||||
|
return ClientboundKeepAliveConfigurationPacket(buffer.readLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ClientboundPingPacket(val id: Int) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundPingPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundPingPacket) {
|
||||||
|
buffer.writeInt(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundPingPacket = ClientboundPingPacket(buffer.readInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.readVarInt
|
||||||
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
|
||||||
|
public data class ClientboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundSelectKnownPacksPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundSelectKnownPacksPacket) {
|
||||||
|
buffer.writeVarInt(value.knownPacks.size)
|
||||||
|
value.knownPacks.forEach { KnownPacks.encode(buffer, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundSelectKnownPacksPacket {
|
||||||
|
val packsCount = buffer.readVarInt()
|
||||||
|
val packs = List(packsCount) { KnownPacks.decode(buffer) }
|
||||||
|
return ClientboundSelectKnownPacksPacket(packs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-9
@@ -5,28 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.configuration
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import cn.rtast.libmc.common.readMcString
|
import cn.rtast.libmc.common.readMcString
|
||||||
import cn.rtast.libmc.common.writeMcString
|
import cn.rtast.libmc.common.writeMcString
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class KnownPacks(
|
public data class KnownPacks(val namespace: String, val id: String, val version: String) {
|
||||||
val namespace: String,
|
|
||||||
val id: String,
|
|
||||||
val version: String,
|
|
||||||
) {
|
|
||||||
public companion object Codec : PacketCodec<KnownPacks> {
|
public companion object Codec : PacketCodec<KnownPacks> {
|
||||||
override fun encode(buffer: _Buffer, value: KnownPacks) {
|
override fun encode(buffer: BytesBuffer, value: KnownPacks) {
|
||||||
buffer.writeMcString(value.namespace)
|
buffer.writeMcString(value.namespace)
|
||||||
buffer.writeMcString(value.id)
|
buffer.writeMcString(value.id)
|
||||||
buffer.writeMcString(value.version)
|
buffer.writeMcString(value.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): KnownPacks {
|
override fun decode(buffer: BytesBuffer): KnownPacks {
|
||||||
val namespace = buffer.readMcString()
|
val namespace = buffer.readMcString()
|
||||||
val id = buffer.readMcString()
|
val id = buffer.readMcString()
|
||||||
val version = buffer.readMcString()
|
val version = buffer.readMcString()
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data object ServerboundAckFinishConfigurationPacket : MinecraftPacket,
|
||||||
|
PacketCodec<ServerboundAckFinishConfigurationPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundAckFinishConfigurationPacket) {}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundAckFinishConfigurationPacket =
|
||||||
|
ServerboundAckFinishConfigurationPacket
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.readVarInt
|
||||||
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.Identifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
|
||||||
|
|
||||||
|
public data class ServerboundCookieResponsePacket(val key: Identifier, val payload: ByteArray?) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundCookieResponsePacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundCookieResponsePacket) {
|
||||||
|
buffer.writeIdentifier(value.key)
|
||||||
|
if (value.payload != null) {
|
||||||
|
buffer.writeBoolean(true)
|
||||||
|
buffer.writeVarInt(value.payload.size)
|
||||||
|
buffer.writeBytes(value.payload)
|
||||||
|
} else {
|
||||||
|
buffer.writeBoolean(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundCookieResponsePacket {
|
||||||
|
val key = buffer.readIdentifier()
|
||||||
|
val hasPayload = buffer.readBoolean()
|
||||||
|
val payload = if (hasPayload) {
|
||||||
|
val length = buffer.readVarInt()
|
||||||
|
buffer.readBytes(length)
|
||||||
|
} else null
|
||||||
|
return ServerboundCookieResponsePacket(key, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as ServerboundCookieResponsePacket
|
||||||
|
|
||||||
|
if (key != other.key) return false
|
||||||
|
if (!payload.contentEquals(other.payload)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = key.hashCode()
|
||||||
|
result = 31 * result + (payload?.contentHashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ServerboundKeepAliveConfigurationPacket(val id: Long) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundKeepAliveConfigurationPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundKeepAliveConfigurationPacket) {
|
||||||
|
buffer.writeLong(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundKeepAliveConfigurationPacket {
|
||||||
|
return ServerboundKeepAliveConfigurationPacket(buffer.readLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ServerboundPongPacket(val id: Int) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundPongPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundPongPacket) {
|
||||||
|
buffer.writeInt(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundPongPacket = ServerboundPongPacket(buffer.readInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.configuration
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.readVarInt
|
||||||
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
|
||||||
|
public data class ServerboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundSelectKnownPacksPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundSelectKnownPacksPacket) {
|
||||||
|
buffer.writeVarInt(value.knownPacks.size)
|
||||||
|
value.knownPacks.forEach { KnownPacks.encode(buffer, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundSelectKnownPacksPacket {
|
||||||
|
val packsCount = buffer.readVarInt()
|
||||||
|
val packs = List(packsCount) { KnownPacks.decode(buffer) }
|
||||||
|
return ServerboundSelectKnownPacksPacket(packs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.handshake
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.writeMcString
|
||||||
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent
|
||||||
|
|
||||||
|
public data class ServerboundHandshakePacket(
|
||||||
|
val protocolVersion: Int,
|
||||||
|
val serverAddress: String,
|
||||||
|
val serverPort: UShort,
|
||||||
|
val intent: HandshakeIntent,
|
||||||
|
) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundHandshakePacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundHandshakePacket) {
|
||||||
|
buffer.writeVarInt(value.protocolVersion)
|
||||||
|
buffer.writeMcString(value.serverAddress)
|
||||||
|
buffer.writeShort(value.serverPort.toShort())
|
||||||
|
buffer.writeVarInt(value.intent.intentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundHandshakePacket = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.login
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.readMcString
|
||||||
|
|
||||||
|
public data class ClientboundDisconnectLoginPacket(val reason: String) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundDisconnectLoginPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket {
|
||||||
|
val reasonJson = buffer.readMcString()
|
||||||
|
return ClientboundDisconnectLoginPacket(reason = reasonJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.login
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.readUuid
|
||||||
|
import cn.rtast.libmc.protocol.profile.GameProfile
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundLoginSuccessPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundLoginSuccessPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundLoginSuccessPacket {
|
||||||
|
val gameProfile = GameProfile.decode(buffer)
|
||||||
|
val sessionId = buffer.readUuid()
|
||||||
|
return ClientboundLoginSuccessPacket(gameProfile, sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.login
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data object ServerboundLoginAcknowledgedPacket : MinecraftPacket,
|
||||||
|
PacketCodec<ServerboundLoginAcknowledgedPacket> {
|
||||||
|
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundLoginAcknowledgedPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundLoginAcknowledgedPacket = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.login
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.writeMcString
|
||||||
|
import cn.rtast.libmc.common.writeUuid
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
public data class ServerboundLoginStartPacket(val username: String, val playerUuid: Uuid) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundLoginStartPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundLoginStartPacket) {
|
||||||
|
buffer.writeMcString(value.username)
|
||||||
|
buffer.writeUuid(value.playerUuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundLoginStartPacket = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.protocol.util.readMinimalTextNbt
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
|
||||||
|
public data class ClientboundDisconnectPlayPacket(val reason: String) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundDisconnectPlayPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectPlayPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundDisconnectPlayPacket {
|
||||||
|
val reasonText = buffer.readMinimalTextNbt()
|
||||||
|
return ClientboundDisconnectPlayPacket(reason = reasonText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ClientboundKeepAlivePlayPacket(val id: Long) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundKeepAlivePlayPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAlivePlayPacket) {
|
||||||
|
buffer.writeLong(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundKeepAlivePlayPacket =
|
||||||
|
ClientboundKeepAlivePlayPacket(buffer.readLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-12
@@ -5,16 +5,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.play
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
import cn.rtast.libmc.chat.protocol.*
|
|
||||||
import cn.rtast.libmc.common.MinecraftPacket
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import cn.rtast.libmc.common.readVarInt
|
import cn.rtast.libmc.common.readVarInt
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.BlockPos
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.GameMode
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.Identifier
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.readBlockPos
|
||||||
|
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
|
||||||
|
|
||||||
internal data class ClientboundLoginPlayPacket(
|
public data class ClientboundLoginPlayPacket(
|
||||||
val entityId: Int,
|
val entityId: Int,
|
||||||
val isHardcore: Boolean,
|
val isHardcore: Boolean,
|
||||||
val dimensionNames: List<Identifier>,
|
val dimensionNames: List<Identifier>,
|
||||||
@@ -38,12 +41,10 @@ internal data class ClientboundLoginPlayPacket(
|
|||||||
val seaLevel: Int,
|
val seaLevel: Int,
|
||||||
val isOnlineMode: Boolean,
|
val isOnlineMode: Boolean,
|
||||||
val enforceSecureChat: Boolean,
|
val enforceSecureChat: Boolean,
|
||||||
) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
) : MinecraftPacket {
|
||||||
override val packetId: Int = 0x31
|
public companion object Codec : PacketCodec<ClientboundLoginPlayPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundLoginPlayPacket) {}
|
||||||
companion object Codec : PacketCodec<ClientboundLoginPlayPacket> {
|
override fun decode(buffer: BytesBuffer): ClientboundLoginPlayPacket {
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundLoginPlayPacket) {}
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundLoginPlayPacket {
|
|
||||||
val entityId = buffer.readInt()
|
val entityId = buffer.readInt()
|
||||||
val isHardcore = buffer.readBoolean()
|
val isHardcore = buffer.readBoolean()
|
||||||
val dimensionNamesCount = buffer.readVarInt()
|
val dimensionNamesCount = buffer.readVarInt()
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ClientboundPingPlayPacket(val id: Int) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ClientboundPingPlayPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundPingPlayPacket) {
|
||||||
|
buffer.writeInt(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundPingPlayPacket {
|
||||||
|
return ClientboundPingPlayPacket(id = buffer.readInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-14
@@ -5,16 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.packet.play
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
import cn.rtast.libmc.chat.chat.ChatFilterType
|
|
||||||
import cn.rtast.libmc.chat.chat.PreviousMessageEntry
|
|
||||||
import cn.rtast.libmc.chat.packet.PacketDirection
|
|
||||||
import cn.rtast.libmc.chat.util.writeMinimalTextNbt
|
|
||||||
import cn.rtast.libmc.common.*
|
import cn.rtast.libmc.common.*
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.protocol.chat.ChatFilterType
|
||||||
|
import cn.rtast.libmc.protocol.chat.PreviousMessageEntry
|
||||||
|
import cn.rtast.libmc.protocol.util.writeMinimalTextNbt
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
internal data class ClientboundPlayerChatMessagePacket(
|
public data class ClientboundPlayerChatMessagePacket(
|
||||||
val globalIndex: Int,
|
val globalIndex: Int,
|
||||||
val sender: Uuid,
|
val sender: Uuid,
|
||||||
val index: Int,
|
val index: Int,
|
||||||
@@ -29,11 +29,9 @@ internal data class ClientboundPlayerChatMessagePacket(
|
|||||||
val chatType: Int,
|
val chatType: Int,
|
||||||
val senderName: String,
|
val senderName: String,
|
||||||
val targetName: String?,
|
val targetName: String?,
|
||||||
) : MinecraftPacket, PacketDirection.ClientboundPacket {
|
) : MinecraftPacket {
|
||||||
override val packetId: Int = 0x41
|
public companion object Codec : PacketCodec<ClientboundPlayerChatMessagePacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundPlayerChatMessagePacket) {
|
||||||
companion object Codec : PacketCodec<ClientboundPlayerChatMessagePacket> {
|
|
||||||
override fun encode(buffer: _Buffer, value: ClientboundPlayerChatMessagePacket) {
|
|
||||||
buffer.writeVarInt(value.globalIndex)
|
buffer.writeVarInt(value.globalIndex)
|
||||||
buffer.writeUuid(value.sender)
|
buffer.writeUuid(value.sender)
|
||||||
buffer.writeVarInt(value.index)
|
buffer.writeVarInt(value.index)
|
||||||
@@ -68,7 +66,8 @@ internal data class ClientboundPlayerChatMessagePacket(
|
|||||||
value.targetName?.let { buffer.writeMinimalTextNbt(it) }
|
value.targetName?.let { buffer.writeMinimalTextNbt(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): ClientboundPlayerChatMessagePacket = throw UnsupportedOperationException() // TODO
|
override fun decode(buffer: BytesBuffer): ClientboundPlayerChatMessagePacket =
|
||||||
|
throw UnsupportedOperationException() // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -82,7 +81,6 @@ internal data class ClientboundPlayerChatMessagePacket(
|
|||||||
if (timestamp != other.timestamp) return false
|
if (timestamp != other.timestamp) return false
|
||||||
if (salt != other.salt) return false
|
if (salt != other.salt) return false
|
||||||
if (chatType != other.chatType) return false
|
if (chatType != other.chatType) return false
|
||||||
if (packetId != other.packetId) return false
|
|
||||||
if (sender != other.sender) return false
|
if (sender != other.sender) return false
|
||||||
if (!messageSignature.contentEquals(other.messageSignature)) return false
|
if (!messageSignature.contentEquals(other.messageSignature)) return false
|
||||||
if (message != other.message) return false
|
if (message != other.message) return false
|
||||||
@@ -102,7 +100,6 @@ internal data class ClientboundPlayerChatMessagePacket(
|
|||||||
result = 31 * result + timestamp.hashCode()
|
result = 31 * result + timestamp.hashCode()
|
||||||
result = 31 * result + salt.hashCode()
|
result = 31 * result + salt.hashCode()
|
||||||
result = 31 * result + chatType
|
result = 31 * result + chatType
|
||||||
result = 31 * result + packetId
|
|
||||||
result = 31 * result + sender.hashCode()
|
result = 31 * result + sender.hashCode()
|
||||||
result = 31 * result + (messageSignature?.contentHashCode() ?: 0)
|
result = 31 * result + (messageSignature?.contentHashCode() ?: 0)
|
||||||
result = 31 * result + message.hashCode()
|
result = 31 * result + message.hashCode()
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data object ClientboundStartConfigurationPacket : MinecraftPacket,
|
||||||
|
PacketCodec<ClientboundStartConfigurationPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ClientboundStartConfigurationPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ClientboundStartConfigurationPacket {
|
||||||
|
return ClientboundStartConfigurationPacket
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
import cn.rtast.libmc.common.writeMcString
|
||||||
|
import cn.rtast.libmc.common.writeVarInt
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
|
public data class ServerboundChatMessagePacket(
|
||||||
|
val message: String,
|
||||||
|
val timestamp: Long = Clock.System.now().toEpochMilliseconds(),
|
||||||
|
val salt: Long = 0L,
|
||||||
|
) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundChatMessagePacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundChatMessagePacket) {
|
||||||
|
buffer.writeMcString(value.message)
|
||||||
|
buffer.writeLong(value.timestamp)
|
||||||
|
buffer.writeLong(value.salt)
|
||||||
|
buffer.writeBoolean(false) // has signature
|
||||||
|
buffer.writeVarInt(0) // message count
|
||||||
|
buffer.writeBytes(byteArrayOf(0, 0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundChatMessagePacket = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public object ServerboundConfigurationAcknowledgedPacket : MinecraftPacket,
|
||||||
|
PacketCodec<ServerboundConfigurationAcknowledgedPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundConfigurationAcknowledgedPacket) {}
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundConfigurationAcknowledgedPacket {
|
||||||
|
return ServerboundConfigurationAcknowledgedPacket
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ServerboundKeepAlivePlayPacket(val id: Long) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundKeepAlivePlayPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundKeepAlivePlayPacket) {
|
||||||
|
buffer.writeLong(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundKeepAlivePlayPacket =
|
||||||
|
ServerboundKeepAlivePlayPacket(buffer.readLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.packet.play
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
|
import cn.rtast.libmc.common.packet.MinecraftPacket
|
||||||
|
|
||||||
|
public data class ServerboundPongPlayPacket(val id: Int) : MinecraftPacket {
|
||||||
|
public companion object Codec : PacketCodec<ServerboundPongPlayPacket> {
|
||||||
|
override fun encode(buffer: BytesBuffer, value: ServerboundPongPlayPacket) {
|
||||||
|
buffer.writeInt(value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(buffer: BytesBuffer): ServerboundPongPlayPacket {
|
||||||
|
return ServerboundPongPlayPacket(id = buffer.readInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.profile
|
package cn.rtast.libmc.protocol.profile
|
||||||
|
|
||||||
import cn.rtast.libmc.common.*
|
import cn.rtast.libmc.common.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -24,14 +24,14 @@ public data class GameProfile(
|
|||||||
val signature: String?,
|
val signature: String?,
|
||||||
) {
|
) {
|
||||||
public companion object Codec : PacketCodec<Property> {
|
public companion object Codec : PacketCodec<Property> {
|
||||||
override fun encode(buffer: _Buffer, value: Property) {
|
override fun encode(buffer: BytesBuffer, value: Property) {
|
||||||
buffer.writeMcString(value.name)
|
buffer.writeMcString(value.name)
|
||||||
buffer.writeMcString(value.value)
|
buffer.writeMcString(value.value)
|
||||||
buffer.writeBoolean(value.signature != null)
|
buffer.writeBoolean(value.signature != null)
|
||||||
value.signature?.let { buffer.writeMcString(it) }
|
value.signature?.let { buffer.writeMcString(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): Property {
|
override fun decode(buffer: BytesBuffer): Property {
|
||||||
val name = buffer.readMcString()
|
val name = buffer.readMcString()
|
||||||
val value = buffer.readMcString()
|
val value = buffer.readMcString()
|
||||||
val hasSignature = buffer.readBoolean()
|
val hasSignature = buffer.readBoolean()
|
||||||
@@ -42,14 +42,14 @@ public data class GameProfile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public companion object Codec : PacketCodec<GameProfile> {
|
public companion object Codec : PacketCodec<GameProfile> {
|
||||||
override fun encode(buffer: _Buffer, value: GameProfile) {
|
override fun encode(buffer: BytesBuffer, value: GameProfile) {
|
||||||
buffer.writeUuid(value.uuid)
|
buffer.writeUuid(value.uuid)
|
||||||
buffer.writeMcString(value.username)
|
buffer.writeMcString(value.username)
|
||||||
buffer.writeVarInt(value.properties.size) // prefixed array
|
buffer.writeVarInt(value.properties.size) // prefixed array
|
||||||
value.properties.forEach { prop -> Property.encode(buffer, prop) }
|
value.properties.forEach { prop -> Property.encode(buffer, prop) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): GameProfile {
|
override fun decode(buffer: BytesBuffer): GameProfile {
|
||||||
val uuid = buffer.readUuid()
|
val uuid = buffer.readUuid()
|
||||||
val username = buffer.readMcString()
|
val username = buffer.readMcString()
|
||||||
val propertyCount = buffer.readVarInt()
|
val propertyCount = buffer.readVarInt()
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.protocol
|
||||||
|
|
||||||
|
import cn.rtast.libmc.protocol.packet.configuration.*
|
||||||
|
import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ClientboundDisconnectLoginPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ClientboundLoginSuccessPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginStartPacket
|
||||||
|
import cn.rtast.libmc.protocol.packet.play.*
|
||||||
|
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
|
||||||
|
import cn.rtast.libmc.protocol.protocol.state.ProtocolStateRegistry
|
||||||
|
|
||||||
|
internal object GameProtocols {
|
||||||
|
val clientboundGameProtocols = ProtocolStateRegistry().apply {
|
||||||
|
register(ProtocolState.CONFIGURATION) {
|
||||||
|
register(0x00, ClientboundCookieRequestPacket)
|
||||||
|
register(0x01, ClientboundCustomPayloadPacket)
|
||||||
|
register(0x02, ClientboundDisconnectConfigurationPacket)
|
||||||
|
register(0x03, ClientboundFinishConfigurationPacket)
|
||||||
|
register(0x04, ClientboundKeepAliveConfigurationPacket)
|
||||||
|
register(0x05, ClientboundPingPacket)
|
||||||
|
register(0x0E, ClientboundSelectKnownPacksPacket)
|
||||||
|
}
|
||||||
|
register(ProtocolState.LOGIN) {
|
||||||
|
register(0x00, ClientboundDisconnectLoginPacket)
|
||||||
|
register(0x02, ClientboundLoginSuccessPacket)
|
||||||
|
}
|
||||||
|
register(ProtocolState.PLAY) {
|
||||||
|
register(0x2C, ClientboundKeepAlivePlayPacket)
|
||||||
|
register(0x2E, ClientboundLoginPlayPacket)
|
||||||
|
register(0x3A, ClientboundPingPlayPacket)
|
||||||
|
register(0x3D, ClientboundPlayerChatMessagePacket)
|
||||||
|
register(0x76, ClientboundStartConfigurationPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val serverboundGameProtocols = ProtocolStateRegistry().apply {
|
||||||
|
register(ProtocolState.HANDSHAKE) {
|
||||||
|
register(0x00, ServerboundHandshakePacket)
|
||||||
|
}
|
||||||
|
register(ProtocolState.CONFIGURATION) {
|
||||||
|
register(0x03, ServerboundAckFinishConfigurationPacket)
|
||||||
|
register(0x04, ServerboundKeepAliveConfigurationPacket)
|
||||||
|
register(0x05, ServerboundPongPacket)
|
||||||
|
register(0x07, ServerboundSelectKnownPacksPacket)
|
||||||
|
}
|
||||||
|
register(ProtocolState.LOGIN) {
|
||||||
|
register(0x00, ServerboundLoginStartPacket)
|
||||||
|
register(0x03, ServerboundLoginAcknowledgedPacket)
|
||||||
|
}
|
||||||
|
register(ProtocolState.PLAY) {
|
||||||
|
register(0x09, ServerboundChatMessagePacket)
|
||||||
|
register(0x0B, ServerboundPongPlayPacket)
|
||||||
|
register(0x0D, ServerboundConfigurationAcknowledgedPacket)
|
||||||
|
register(0x1C, ServerboundKeepAlivePlayPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.protocol
|
||||||
|
|
||||||
|
internal enum class PacketDirection {
|
||||||
|
SERVERBOUND, CLIENTBOUND
|
||||||
|
}
|
||||||
+6
-6
@@ -5,10 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.protocol
|
package cn.rtast.libmc.protocol.protocol.game
|
||||||
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +22,7 @@ public data class BlockPos(val x: Int, val y: Int, val z: Int) {
|
|||||||
private const val PACKED_Y_MASK = 0xFFFL // 12 bits
|
private const val PACKED_Y_MASK = 0xFFFL // 12 bits
|
||||||
private const val PACKED_Z_MASK = 0x3FFFFFFL // 26 bits
|
private const val PACKED_Z_MASK = 0x3FFFFFFL // 26 bits
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): BlockPos {
|
override fun decode(buffer: BytesBuffer): BlockPos {
|
||||||
val packed = buffer.readLong()
|
val packed = buffer.readLong()
|
||||||
val x = (packed shr 38).toInt()
|
val x = (packed shr 38).toInt()
|
||||||
val y = (packed shl 52 shr 52).toInt()
|
val y = (packed shl 52 shr 52).toInt()
|
||||||
@@ -30,7 +30,7 @@ public data class BlockPos(val x: Int, val y: Int, val z: Int) {
|
|||||||
return BlockPos(x, y, z)
|
return BlockPos(x, y, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(buffer: _Buffer, value: BlockPos) {
|
override fun encode(buffer: BytesBuffer, value: BlockPos) {
|
||||||
val xLong = (value.x.toLong() and PACKED_X_MASK)
|
val xLong = (value.x.toLong() and PACKED_X_MASK)
|
||||||
val yLong = (value.y.toLong() and PACKED_Y_MASK)
|
val yLong = (value.y.toLong() and PACKED_Y_MASK)
|
||||||
val zLong = (value.z.toLong() and PACKED_Z_MASK)
|
val zLong = (value.z.toLong() and PACKED_Z_MASK)
|
||||||
@@ -39,5 +39,5 @@ public data class BlockPos(val x: Int, val y: Int, val z: Int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun _Buffer.readBlockPos(): BlockPos = BlockPos.decode(this)
|
internal fun BytesBuffer.readBlockPos(): BlockPos = BlockPos.decode(this)
|
||||||
internal fun _Buffer.writeBlockPos(pos: BlockPos) = BlockPos.encode(this, pos)
|
internal fun BytesBuffer.writeBlockPos(pos: BlockPos) = BlockPos.encode(this, pos)
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.protocol
|
package cn.rtast.libmc.protocol.protocol.game
|
||||||
|
|
||||||
public enum class GameMode(public val id: Byte) {
|
public enum class GameMode(public val id: Byte) {
|
||||||
Survival(0),
|
Survival(0),
|
||||||
+6
-6
@@ -5,10 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.protocol
|
package cn.rtast.libmc.protocol.protocol.game
|
||||||
|
|
||||||
import cn.rtast.libmc.common.PacketCodec
|
import cn.rtast.libmc.common.PacketCodec
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import cn.rtast.libmc.common.readMcString
|
import cn.rtast.libmc.common.readMcString
|
||||||
import cn.rtast.libmc.common.writeMcString
|
import cn.rtast.libmc.common.writeMcString
|
||||||
import kotlin.jvm.JvmInline
|
import kotlin.jvm.JvmInline
|
||||||
@@ -26,13 +26,13 @@ public value class Identifier(public val full: String) {
|
|||||||
public companion object Codec : PacketCodec<Identifier> {
|
public companion object Codec : PacketCodec<Identifier> {
|
||||||
public fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path")
|
public fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path")
|
||||||
|
|
||||||
override fun encode(buffer: _Buffer, value: Identifier) {
|
override fun encode(buffer: BytesBuffer, value: Identifier) {
|
||||||
buffer.writeMcString(value.toString())
|
buffer.writeMcString(value.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(buffer: _Buffer): Identifier = Identifier(buffer.readMcString())
|
override fun decode(buffer: BytesBuffer): Identifier = Identifier(buffer.readMcString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun _Buffer.readIdentifier(): Identifier = Identifier.decode(this)
|
internal fun BytesBuffer.readIdentifier(): Identifier = Identifier.decode(this)
|
||||||
internal fun _Buffer.writeIdentifier(identifier: Identifier) = Identifier.encode(this, identifier)
|
internal fun BytesBuffer.writeIdentifier(identifier: Identifier) = Identifier.encode(this, identifier)
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.protocol.state
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
public value class HandshakeIntent internal constructor(public val intentID: Int) {
|
||||||
|
public companion object {
|
||||||
|
public val STATUS: HandshakeIntent = HandshakeIntent(1)
|
||||||
|
public val LOGIN: HandshakeIntent = HandshakeIntent(2)
|
||||||
|
public val TRANSFER: HandshakeIntent = HandshakeIntent(3)
|
||||||
|
|
||||||
|
public fun fromID(intentID: Int): HandshakeIntent = when (intentID) {
|
||||||
|
1 -> STATUS; 2 -> LOGIN; 3 -> TRANSFER
|
||||||
|
else -> throw IllegalArgumentException("Unknown Handshake Intent ID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-3
@@ -5,11 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.protocol
|
package cn.rtast.libmc.protocol.protocol.state
|
||||||
|
|
||||||
internal enum class ProtocolState {
|
public enum class ProtocolState {
|
||||||
HANDSHAKE,
|
HANDSHAKE,
|
||||||
LOGIN,
|
LOGIN,
|
||||||
CONFIGURATION,
|
CONFIGURATION,
|
||||||
PLAY
|
PLAY,
|
||||||
|
DISCONNECTED
|
||||||
}
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.protocol.state
|
||||||
|
|
||||||
|
import cn.rtast.libmc.common.packet.PacketRegistry
|
||||||
|
import kotlin.enums.enumEntries
|
||||||
|
|
||||||
|
public class ProtocolStateRegistry {
|
||||||
|
// create registries for different state
|
||||||
|
private val registries = enumEntries<ProtocolState>().toTypedArray().associateWith { PacketRegistry() }
|
||||||
|
|
||||||
|
public fun getRegistry(state: ProtocolState): PacketRegistry =
|
||||||
|
requireNotNull(registries[state]) { "No registry found for state $state" }
|
||||||
|
|
||||||
|
public fun register(state: ProtocolState, block: PacketRegistry.() -> Unit) {
|
||||||
|
registries[state]?.apply(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/5
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.util
|
||||||
|
|
||||||
|
internal object CycloneMd5 {
|
||||||
|
const val BLOCK_SIZE = 64
|
||||||
|
const val DIGEST_SIZE = 16
|
||||||
|
const val MIN_PAD_SIZE = 9
|
||||||
|
|
||||||
|
val OID = byteArrayOf(
|
||||||
|
0x2A.toByte(), 0x86.toByte(), 0x48.toByte(), 0x86.toByte(),
|
||||||
|
0xF7.toByte(), 0x0D.toByte(), 0x02.toByte(), 0x05.toByte()
|
||||||
|
)
|
||||||
|
|
||||||
|
private val PADDING = ByteArray(64).apply { this[0] = 0x80.toByte() }
|
||||||
|
|
||||||
|
private val K = intArrayOf(
|
||||||
|
0xD76AA478.toInt(), 0xE8C7B756.toInt(), 0x242070DB, 0xC1BDCEEE.toInt(),
|
||||||
|
0xF57C0FAF.toInt(), 0x4787C62A, 0xA8304613.toInt(), 0xFD469501.toInt(),
|
||||||
|
0x698098D8, 0x8B44F7AF.toInt(), 0xFFFF5BB1.toInt(), 0x895CD7BE.toInt(),
|
||||||
|
0x6B901122, 0xFD987193.toInt(), 0xA679438E.toInt(), 0x49B40821,
|
||||||
|
0xF61E2562.toInt(), 0xC040B340.toInt(), 0x265E5A51, 0xE9B6C7AA.toInt(),
|
||||||
|
0xD62F105D.toInt(), 0x02441453, 0xD8A1E681.toInt(), 0xE7D3FBC8.toInt(),
|
||||||
|
0x21E1CDE6, 0xC33707D6.toInt(), 0xF4D50D87.toInt(), 0x455A14ED,
|
||||||
|
0xA9E3E905.toInt(), 0xFCEFA3F8.toInt(), 0x676F02D9, 0x8D2A4C8A.toInt(),
|
||||||
|
0xFFFA3942.toInt(), 0x8771F681.toInt(), 0x6D9D6122, 0xFDE5380C.toInt(),
|
||||||
|
0xA4BEEA44.toInt(), 0x4BDECFA9, 0xF6BB4B60.toInt(), 0xBEBFBC70.toInt(),
|
||||||
|
0x289B7EC6, 0xEAA127FA.toInt(), 0xD4EF3085.toInt(), 0x04881D05,
|
||||||
|
0xD9D4D039.toInt(), 0xE6DB99E5.toInt(), 0x1FA27CF8, 0xC4AC5665.toInt(),
|
||||||
|
0xF4292244.toInt(), 0x432AFF97, 0xAB9423A7.toInt(), 0xFC93A039.toInt(),
|
||||||
|
0x655B59C3, 0x8F0CCC92.toInt(), 0xFFEFF47D.toInt(), 0x85845DD1.toInt(),
|
||||||
|
0x6FA87E4F, 0xFE2CE6E0.toInt(), 0xA3014314.toInt(), 0x4E0811A1,
|
||||||
|
0xF7537E82.toInt(), 0xBD3AF235.toInt(), 0x2AD7D2BB, 0xEB86D391.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
private class Context {
|
||||||
|
val h = IntArray(4)
|
||||||
|
val buffer = ByteArray(64)
|
||||||
|
val x = IntArray(16)
|
||||||
|
var size: Int = 0
|
||||||
|
var totalSize: Long = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compute(data: ByteArray): ByteArray {
|
||||||
|
val digest = ByteArray(DIGEST_SIZE)
|
||||||
|
val context = Context()
|
||||||
|
initContext(context)
|
||||||
|
updateContext(context, data, 0, data.size)
|
||||||
|
finalContext(context, digest)
|
||||||
|
return digest
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeToHex(data: ByteArray): String {
|
||||||
|
return compute(data).toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeToHex(text: String): String {
|
||||||
|
return compute(text.encodeToByteArray()).toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeToBytes(data: ByteArray): ByteArray = compute(data)
|
||||||
|
|
||||||
|
private fun initContext(context: Context) {
|
||||||
|
context.h[0] = 0x67452301
|
||||||
|
context.h[1] = 0xEFCDAB89.toInt()
|
||||||
|
context.h[2] = 0x98BADCFE.toInt()
|
||||||
|
context.h[3] = 0x10325476
|
||||||
|
context.size = 0
|
||||||
|
context.totalSize = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateContext(context: Context, data: ByteArray, offset: Int, length: Int) {
|
||||||
|
var dataOffset = offset
|
||||||
|
var remLength = length
|
||||||
|
|
||||||
|
while (remLength > 0) {
|
||||||
|
val n = minOf(remLength, 64 - context.size)
|
||||||
|
data.copyInto(context.buffer, context.size, dataOffset, dataOffset + n)
|
||||||
|
|
||||||
|
context.size += n
|
||||||
|
context.totalSize += n
|
||||||
|
dataOffset += n
|
||||||
|
remLength -= n
|
||||||
|
|
||||||
|
if (context.size == 64) {
|
||||||
|
processBlock(context)
|
||||||
|
context.size = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finalContext(context: Context, digest: ByteArray) {
|
||||||
|
var totalBits: Long = context.totalSize * 8L
|
||||||
|
val paddingSize = if (context.size < 56) {
|
||||||
|
56 - context.size
|
||||||
|
} else {
|
||||||
|
64 + 56 - context.size
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContext(context, PADDING, 0, paddingSize)
|
||||||
|
|
||||||
|
for (i in 0 until 8) {
|
||||||
|
context.buffer[56 + i] = (totalBits and 0xFFL).toByte()
|
||||||
|
totalBits = totalBits ushr 8
|
||||||
|
}
|
||||||
|
|
||||||
|
processBlock(context)
|
||||||
|
|
||||||
|
for (i in 0 until (DIGEST_SIZE / 4)) {
|
||||||
|
store32le(context.h[i], digest, i * 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processBlock(context: Context) {
|
||||||
|
var a = context.h[0]
|
||||||
|
var b = context.h[1]
|
||||||
|
var c = context.h[2]
|
||||||
|
var d = context.h[3]
|
||||||
|
|
||||||
|
val x = context.x
|
||||||
|
|
||||||
|
for (i in 0 until 16) {
|
||||||
|
x[i] = load32le(context.buffer, i * 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round 1
|
||||||
|
a = ff(a, b, c, d, x[0], 7, K[0])
|
||||||
|
d = ff(d, a, b, c, x[1], 12, K[1])
|
||||||
|
c = ff(c, d, a, b, x[2], 17, K[2])
|
||||||
|
b = ff(b, c, d, a, x[3], 22, K[3])
|
||||||
|
a = ff(a, b, c, d, x[4], 7, K[4])
|
||||||
|
d = ff(d, a, b, c, x[5], 12, K[5])
|
||||||
|
c = ff(c, d, a, b, x[6], 17, K[6])
|
||||||
|
b = ff(b, c, d, a, x[7], 22, K[7])
|
||||||
|
a = ff(a, b, c, d, x[8], 7, K[8])
|
||||||
|
d = ff(d, a, b, c, x[9], 12, K[9])
|
||||||
|
c = ff(c, d, a, b, x[10], 17, K[10])
|
||||||
|
b = ff(b, c, d, a, x[11], 22, K[11])
|
||||||
|
a = ff(a, b, c, d, x[12], 7, K[12])
|
||||||
|
d = ff(d, a, b, c, x[13], 12, K[13])
|
||||||
|
c = ff(c, d, a, b, x[14], 17, K[14])
|
||||||
|
b = ff(b, c, d, a, x[15], 22, K[15])
|
||||||
|
|
||||||
|
// Round 2
|
||||||
|
a = gg(a, b, c, d, x[1], 5, K[16])
|
||||||
|
d = gg(d, a, b, c, x[6], 9, K[17])
|
||||||
|
c = gg(c, d, a, b, x[11], 14, K[18])
|
||||||
|
b = gg(b, c, d, a, x[0], 20, K[19])
|
||||||
|
a = gg(a, b, c, d, x[5], 5, K[20])
|
||||||
|
d = gg(d, a, b, c, x[10], 9, K[21])
|
||||||
|
c = gg(c, d, a, b, x[15], 14, K[22])
|
||||||
|
b = gg(b, c, d, a, x[4], 20, K[23])
|
||||||
|
a = gg(a, b, c, d, x[9], 5, K[24])
|
||||||
|
d = gg(d, a, b, c, x[14], 9, K[25])
|
||||||
|
c = gg(c, d, a, b, x[3], 14, K[26])
|
||||||
|
b = gg(b, c, d, a, x[8], 20, K[27])
|
||||||
|
a = gg(a, b, c, d, x[13], 5, K[28])
|
||||||
|
d = gg(d, a, b, c, x[2], 9, K[29])
|
||||||
|
c = gg(c, d, a, b, x[7], 14, K[30])
|
||||||
|
b = gg(b, c, d, a, x[12], 20, K[31])
|
||||||
|
|
||||||
|
// Round 3
|
||||||
|
a = hh(a, b, c, d, x[5], 4, K[32])
|
||||||
|
d = hh(d, a, b, c, x[8], 11, K[33])
|
||||||
|
c = hh(c, d, a, b, x[11], 16, K[34])
|
||||||
|
b = hh(b, c, d, a, x[14], 23, K[35])
|
||||||
|
a = hh(a, b, c, d, x[1], 4, K[36])
|
||||||
|
d = hh(d, a, b, c, x[4], 11, K[37])
|
||||||
|
c = hh(c, d, a, b, x[7], 16, K[38])
|
||||||
|
b = hh(b, c, d, a, x[10], 23, K[39])
|
||||||
|
a = hh(a, b, c, d, x[13], 4, K[40])
|
||||||
|
d = hh(d, a, b, c, x[0], 11, K[41])
|
||||||
|
c = hh(c, d, a, b, x[3], 16, K[42])
|
||||||
|
b = hh(b, c, d, a, x[6], 23, K[43])
|
||||||
|
a = hh(a, b, c, d, x[9], 4, K[44])
|
||||||
|
d = hh(d, a, b, c, x[12], 11, K[45])
|
||||||
|
c = hh(c, d, a, b, x[15], 16, K[46])
|
||||||
|
b = hh(b, c, d, a, x[2], 23, K[47])
|
||||||
|
|
||||||
|
// Round 4
|
||||||
|
a = ii(a, b, c, d, x[0], 6, K[48])
|
||||||
|
d = ii(d, a, b, c, x[7], 10, K[49])
|
||||||
|
c = ii(c, d, a, b, x[14], 15, K[50])
|
||||||
|
b = ii(b, c, d, a, x[5], 21, K[51])
|
||||||
|
a = ii(a, b, c, d, x[12], 6, K[52])
|
||||||
|
d = ii(d, a, b, c, x[3], 10, K[53])
|
||||||
|
c = ii(c, d, a, b, x[10], 15, K[54])
|
||||||
|
b = ii(b, c, d, a, x[1], 21, K[55])
|
||||||
|
a = ii(a, b, c, d, x[8], 6, K[56])
|
||||||
|
d = ii(d, a, b, c, x[15], 10, K[57])
|
||||||
|
c = ii(c, d, a, b, x[6], 15, K[58])
|
||||||
|
b = ii(b, c, d, a, x[13], 21, K[59])
|
||||||
|
a = ii(a, b, c, d, x[4], 6, K[60])
|
||||||
|
d = ii(d, a, b, c, x[11], 10, K[61])
|
||||||
|
c = ii(c, d, a, b, x[2], 15, K[62])
|
||||||
|
b = ii(b, c, d, a, x[9], 21, K[63])
|
||||||
|
|
||||||
|
context.h[0] += a
|
||||||
|
context.h[1] += b
|
||||||
|
context.h[2] += c
|
||||||
|
context.h[3] += d
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.toHexString(): String {
|
||||||
|
val hexChars = CharArray(size * 2)
|
||||||
|
val hexArray = "0123456789abcdef".toCharArray()
|
||||||
|
for (i in indices) {
|
||||||
|
val v = this[i].toInt() and 0xFF
|
||||||
|
hexChars[i * 2] = hexArray[v ushr 4]
|
||||||
|
hexChars[i * 2 + 1] = hexArray[v and 0x0F]
|
||||||
|
}
|
||||||
|
return hexChars.concatToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rol32(a: Int, s: Int): Int = (a shl s) or (a ushr (32 - s))
|
||||||
|
|
||||||
|
private fun load32le(buf: ByteArray, offset: Int): Int {
|
||||||
|
return (buf[offset].toInt() and 0xFF) or
|
||||||
|
((buf[offset + 1].toInt() and 0xFF) shl 8) or
|
||||||
|
((buf[offset + 2].toInt() and 0xFF) shl 16) or
|
||||||
|
((buf[offset + 3].toInt() and 0xFF) shl 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun store32le(val32: Int, buf: ByteArray, offset: Int) {
|
||||||
|
buf[offset] = (val32 and 0xFF).toByte()
|
||||||
|
buf[offset + 1] = ((val32 ushr 8) and 0xFF).toByte()
|
||||||
|
buf[offset + 2] = ((val32 ushr 16) and 0xFF).toByte()
|
||||||
|
buf[offset + 3] = ((val32 ushr 24) and 0xFF).toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun f(x: Int, y: Int, z: Int): Int = (x and y) or (x.inv() and z)
|
||||||
|
private fun g(x: Int, y: Int, z: Int): Int = (x and z) or (y and z.inv())
|
||||||
|
private fun h(x: Int, y: Int, z: Int): Int = x xor y xor z
|
||||||
|
private fun i(x: Int, y: Int, z: Int): Int = y xor (x or z.inv())
|
||||||
|
|
||||||
|
private fun ff(a: Int, b: Int, c: Int, d: Int, x: Int, s: Int, k: Int): Int =
|
||||||
|
rol32(a + f(b, c, d) + x + k, s) + b
|
||||||
|
|
||||||
|
private fun gg(a: Int, b: Int, c: Int, d: Int, x: Int, s: Int, k: Int): Int =
|
||||||
|
rol32(a + g(b, c, d) + x + k, s) + b
|
||||||
|
|
||||||
|
private fun hh(a: Int, b: Int, c: Int, d: Int, x: Int, s: Int, k: Int): Int =
|
||||||
|
rol32(a + h(b, c, d) + x + k, s) + b
|
||||||
|
|
||||||
|
private fun ii(a: Int, b: Int, c: Int, d: Int, x: Int, s: Int, k: Int): Int =
|
||||||
|
rol32(a + i(b, c, d) + x + k, s) + b
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ByteArray.digest(): ByteArray = CycloneMd5.computeToBytes(this)
|
||||||
+4
-4
@@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.chat.util
|
package cn.rtast.libmc.protocol.util
|
||||||
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tmp
|
* tmp
|
||||||
*/
|
*/
|
||||||
internal fun _Buffer.writeMinimalTextNbt(text: String) {
|
internal fun BytesBuffer.writeMinimalTextNbt(text: String) {
|
||||||
writeByte(0x0A)
|
writeByte(0x0A)
|
||||||
writeByte(0x08)
|
writeByte(0x08)
|
||||||
val keyBytes = "text".encodeToByteArray()
|
val keyBytes = "text".encodeToByteArray()
|
||||||
@@ -25,7 +25,7 @@ internal fun _Buffer.writeMinimalTextNbt(text: String) {
|
|||||||
writeByte(0x00)
|
writeByte(0x00)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun _Buffer.readMinimalTextNbt(): String {
|
internal fun BytesBuffer.readMinimalTextNbt(): String {
|
||||||
val rootTagType = readByte().toInt()
|
val rootTagType = readByte().toInt()
|
||||||
if (rootTagType != 0x0A) return ""
|
if (rootTagType != 0x0A) return ""
|
||||||
var resultText = ""
|
var resultText = ""
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.libmc.protocol.util
|
||||||
|
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* translate from PHP code
|
||||||
|
* ref: https://gist.github.com/TuxCoding/2b6a00a8fc21fd3b88375f03c9e2e603
|
||||||
|
*/
|
||||||
|
public fun generateOfflineUuid(username: String): Uuid {
|
||||||
|
val data = "OfflinePlayer:$username".encodeToByteArray().digest()
|
||||||
|
data[6] = ((data[6].toInt() and 0x0F) or 0x30).toByte()
|
||||||
|
data[8] = ((data[8].toInt() and 0x3F) or 0x80).toByte()
|
||||||
|
val hexChars = "0123456789abcdef"
|
||||||
|
val sb = StringBuilder(36)
|
||||||
|
for (i in 0 until 16) {
|
||||||
|
if (i == 4 || i == 6 || i == 8 || i == 10) sb.append('-')
|
||||||
|
val v = data[i].toInt() and 0xFF
|
||||||
|
sb.append(hexChars[v ushr 4])
|
||||||
|
sb.append(hexChars[v and 0x0F])
|
||||||
|
}
|
||||||
|
return Uuid.parse(sb.toString())
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import cn.rtast.libmc.protocol.client.MinecraftClient
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TestClient {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test client`() = runTest {
|
||||||
|
val cli = MinecraftClient("127.0.0.1", 25565, "123")
|
||||||
|
cli.connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
package cn.rtast.libmc.mcping.rconlib
|
package cn.rtast.libmc.mcping.rconlib
|
||||||
|
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
|
|
||||||
internal fun _Buffer.writeNull() = writeByte(0x00)
|
internal fun BytesBuffer.writeNull() = writeByte(0x00)
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
package cn.rtast.libmc.mcping.rconlib
|
package cn.rtast.libmc.mcping.rconlib
|
||||||
|
|
||||||
import cn.rtast.libmc.common.ByteOrder
|
import cn.rtast.libmc.common.ByteOrder
|
||||||
import cn.rtast.libmc.common._Buffer
|
import cn.rtast.libmc.common.BytesBuffer
|
||||||
import cn.rtast.libmc.common._ReadChannel
|
import cn.rtast.libmc.common.ReadChannel
|
||||||
import cn.rtast.libmc.common._WriteChannel
|
import cn.rtast.libmc.common.WriteChannel
|
||||||
|
|
||||||
internal abstract class Packet {
|
internal abstract class Packet {
|
||||||
abstract val requestId: Int
|
abstract val requestId: Int
|
||||||
@@ -25,7 +25,7 @@ internal abstract class Packet {
|
|||||||
open val length: Int
|
open val length: Int
|
||||||
get() = 4 + 4 + payloadLength + 2
|
get() = 4 + 4 + payloadLength + 2
|
||||||
|
|
||||||
fun writePayload(buf: _Buffer) {
|
fun writePayload(buf: BytesBuffer) {
|
||||||
buf.writeInt(length, ByteOrder.LITTLE_ENDIAN)
|
buf.writeInt(length, ByteOrder.LITTLE_ENDIAN)
|
||||||
buf.writeInt(requestId, ByteOrder.LITTLE_ENDIAN)
|
buf.writeInt(requestId, ByteOrder.LITTLE_ENDIAN)
|
||||||
buf.writeInt(type, ByteOrder.LITTLE_ENDIAN)
|
buf.writeInt(type, ByteOrder.LITTLE_ENDIAN)
|
||||||
@@ -35,11 +35,11 @@ internal abstract class Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object Codec {
|
companion object Codec {
|
||||||
fun decode(channel: _ReadChannel): ResponsePacket {
|
fun decode(channel: ReadChannel): ResponsePacket {
|
||||||
val length = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
val length = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
||||||
val requestId = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
val requestId = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
||||||
val type = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
val type = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
||||||
val payloadBuffer = _Buffer()
|
val payloadBuffer = BytesBuffer()
|
||||||
while (true) {
|
while (true) {
|
||||||
val b = channel.readByte()
|
val b = channel.readByte()
|
||||||
if (b == 0x00.toByte()) break
|
if (b == 0x00.toByte()) break
|
||||||
@@ -73,11 +73,11 @@ internal data class ResponsePacket(
|
|||||||
) : Packet()
|
) : Packet()
|
||||||
|
|
||||||
|
|
||||||
internal fun _WriteChannel.sendPacket(packet: Packet) {
|
internal fun WriteChannel.sendPacket(packet: Packet) {
|
||||||
val buf = _Buffer()
|
val buf = BytesBuffer()
|
||||||
packet.writePayload(buf)
|
packet.writePayload(buf)
|
||||||
writeFully(buf.toByteArray())
|
writeFully(buf.toByteArray())
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun _ReadChannel.readPacket(): ResponsePacket = Packet.decode(this)
|
internal fun ReadChannel.readPacket(): ResponsePacket = Packet.decode(this)
|
||||||
@@ -9,9 +9,9 @@
|
|||||||
package cn.rtast.libmc.mcping.rconlib
|
package cn.rtast.libmc.mcping.rconlib
|
||||||
|
|
||||||
import cn.rtast.libmc.common.LibMCContext
|
import cn.rtast.libmc.common.LibMCContext
|
||||||
import cn.rtast.libmc.common._ReadChannel
|
import cn.rtast.libmc.common.ReadChannel
|
||||||
import cn.rtast.libmc.common._Socket
|
import cn.rtast.libmc.common.Socket
|
||||||
import cn.rtast.libmc.common._WriteChannel
|
import cn.rtast.libmc.common.WriteChannel
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
import kotlin.jvm.JvmOverloads
|
import kotlin.jvm.JvmOverloads
|
||||||
|
|
||||||
@@ -20,13 +20,13 @@ public class RCONClient internal constructor(
|
|||||||
private val port: Int,
|
private val port: Int,
|
||||||
private val context: LibMCContext,
|
private val context: LibMCContext,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
private var socket: _Socket? = null
|
private var socket: Socket? = null
|
||||||
private var readChannel: _ReadChannel? = null
|
private var readChannel: ReadChannel? = null
|
||||||
private var writeChannel: _WriteChannel? = null
|
private var writeChannel: WriteChannel? = null
|
||||||
private var currentRequestId = 1
|
private var currentRequestId = 1
|
||||||
|
|
||||||
public fun connect(password: String): Boolean {
|
public fun connect(password: String): Boolean {
|
||||||
socket = _Socket(host, port, context)
|
socket = Socket(host, port, context)
|
||||||
readChannel = socket!!.openReadChannel()
|
readChannel = socket!!.openReadChannel()
|
||||||
writeChannel = socket!!.openWriteChannel()
|
writeChannel = socket!!.openWriteChannel()
|
||||||
val authPacket = AuthPacket(password, currentRequestId)
|
val authPacket = AuthPacket(password, currentRequestId)
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ rootProject.name = "libmc"
|
|||||||
includeSubModule(":common")
|
includeSubModule(":common")
|
||||||
includeSubModule(":mcping")
|
includeSubModule(":mcping")
|
||||||
includeSubModule(":rconlib")
|
includeSubModule(":rconlib")
|
||||||
includeSubModule(":chat")
|
includeSubModule(":protocol")
|
||||||
//includeSubModule(":nbt")
|
//includeSubModule(":nbt")
|
||||||
|
|
||||||
fun includeSubModule(name: String) = include(name).also {
|
fun includeSubModule(name: String) = include(name).also {
|
||||||
|
|||||||
Reference in New Issue
Block a user