Split Socket implementation, improve performance

This commit is contained in:
2026-09-08 01:16:01 +08:00
parent ebb4ff848d
commit ff14d8bf00
318 files changed
+1109 -2722

No files matched your search

+1
View File
@@ -49,3 +49,4 @@ bin/
**/build/*
/libmc-protocol/src/jvmTest/resources/accessToken.txt
/libmc-protocol-encrypt/src/commonTest/resources/accessToken.txt
/libmc-protocol-context/src/commonTest/resources/accessToken.txt
-6
View File
@@ -346,12 +346,6 @@ A lightweight minecraft client-side protocol library and related library, includ
---
# libmc-mcping
A lightweight module to query Minecraft Java and Bedrock server status, MOTD, and latency
[Use mcping](https://repo.rtast.cn/packages/-/cn.rtast.libmc:mcping)
# libmc-rconlib
Send Command via rcon protocol
+1
View File
@@ -20,6 +20,7 @@ subprojects {
pluginManager.apply("org.jetbrains.kotlin.multiplatform")
pluginManager.apply("maven-publish")
if (project.path.startsWith(":example")) return@subprojects
publishing {
repositories {
maven("https://repo.rtast.cn/packages") {
+10 -4
View File
@@ -12,18 +12,24 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.io)
api(libs.kotlinx.coroutines)
}
jvmMain.dependencies {}
nativeMain.dependencies {
implementation(libs.ktor.network)
implementation(libs.kotlinx.io)
}
// nativeMain.dependencies {
// implementation(libs.ktor.network)
// }
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
}
jvmTest.dependencies {
}
}
compilerOptions.freeCompilerArgs.addAll("-Xexpect-actual-classes")
@@ -1,14 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc
/**
* An object class used to pass some parameters
* that exists only on native targets
*/
public expect class LibMCContext()
@@ -8,6 +8,6 @@
package cn.rtast.libmc.crypto
public interface NetworkCipher {
public suspend fun encrypt(buffer: ByteArray, offset: Int, length: Int)
public suspend fun decrypt(buffer: ByteArray, offset: Int, length: Int)
public fun encrypt(buffer: ByteArray, offset: Int, length: Int)
public fun decrypt(buffer: ByteArray, offset: Int, length: Int)
}
@@ -7,18 +7,23 @@
package cn.rtast.libmc.crypto
import cn.rtast.libmc.network.SocketContext
import cn.rtast.libmc.network.SocketEngine
public data class ProtocolContext(
val rsaEncryptor: RSA1024Encryptor,
val sha1Hasher: Sha1Hasher,
val cipherFactory: (sharedKey: ByteArray) -> NetworkCipher,
val authProvider: AuthenticationProvider?,
)
override val engine: SocketEngine,
) : SocketContext()
public class ProtocolContextBuilder(private val onlineMode: Boolean) {
public lateinit var rsaEncryptor: RSA1024Encryptor
public lateinit var sha1Hasher: Sha1Hasher
public lateinit var cipherFactory: (sharedKey: ByteArray) -> NetworkCipher
public lateinit var authProvider: AuthenticationProvider
public lateinit var socketEngine: SocketEngine
public fun build(): ProtocolContext =
ProtocolContext(
@@ -27,14 +32,15 @@ public class ProtocolContextBuilder(private val onlineMode: Boolean) {
cipherFactory = if (::cipherFactory.isInitialized) cipherFactory else error("cipherFactory is required"),
authProvider = if (onlineMode) {
if (::authProvider.isInitialized) authProvider else error("authProvider is required in online mode")
} else if (::authProvider.isInitialized) authProvider else null
} else if (::authProvider.isInitialized) authProvider else null,
engine = if (::socketEngine.isInitialized) socketEngine else error("SocketEngine is not configured")
)
}
public fun interface RSA1024Encryptor {
public suspend fun encrypt(key: ByteArray, data: ByteArray): ByteArray
public fun encrypt(key: ByteArray, data: ByteArray): ByteArray
}
public fun interface Sha1Hasher {
public suspend fun hash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String
public fun hash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String
}
@@ -5,7 +5,7 @@
*/
package cn.rtast.libmc.stream
package cn.rtast.libmc.network
public enum class ByteOrder {
BIG_ENDIAN, LITTLE_ENDIAN
@@ -0,0 +1,47 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.network
import kotlinx.io.*
public class BytesBuffer {
private val _buffer = Buffer()
public constructor()
public constructor(bytes: ByteArray) {
this._buffer.write(bytes)
}
public fun writeByte(value: Byte): Unit = _buffer.writeByte(value)
public fun writeShort(value: Short): Unit = _buffer.writeShort(value)
public fun writeInt(value: Int): Unit = _buffer.writeInt(value)
public fun writeLong(value: Long): Unit = _buffer.writeLong(value)
public fun writeDouble(value: Double): Unit = _buffer.writeDouble(value)
public fun writeFloat(value: Float): Unit = _buffer.writeFloat(value)
public fun writeBytes(bytes: ByteArray): Unit = _buffer.write(bytes)
public fun writeBoolean(value: Boolean): Unit = _buffer.writeByte(if (value) 0x01 else 0x00)
public fun readByte(): Byte = _buffer.readByte()
public fun readUByte(): UByte = _buffer.readUByte()
public fun readShort(): Short = _buffer.readShort()
public fun readInt(): Int = _buffer.readInt()
public fun readLong(): Long = _buffer.readLong()
public fun readDouble(): Double = _buffer.readDouble()
public fun readFloat(): Float = _buffer.readFloat()
public fun readBytes(length: Int): ByteArray = _buffer.readByteArray(length)
public fun readBoolean(): Boolean = _buffer.readByte() != 0x00.toByte()
public fun toByteArray(): ByteArray = _buffer.readByteArray()
public fun peek(): ByteArray = _buffer.peek().readByteArray()
public fun close(): Unit = _buffer.close()
public val size: Int get() = _buffer.size.toInt()
}
@Suppress("NOTHING_TO_INLINE")
public inline fun ByteArray.wrap(): BytesBuffer = BytesBuffer(this)
@@ -0,0 +1,30 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.network
public interface RawSocket : AutoCloseable {
/**
* Open socket connection
*/
public suspend fun connect()
/**
* An abstract function, used to open tcp socket read channel
*/
public fun openReadChannel(): ReadChannel
/**
* An abstract function, used to open tcp socket write/send channel
*/
public fun openWriteChannel(): WriteChannel
/**
* Close socket
*/
override fun close()
}
@@ -0,0 +1,14 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.network
public interface ReadChannel {
public suspend fun readByte(): Byte
public suspend fun readBytes(length: Int): ByteArray
public suspend fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
}
@@ -0,0 +1,14 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.network
public abstract class SocketContext {
public abstract val engine: SocketEngine
public fun createSocket(host: String, port: Int): RawSocket = engine.create(host, port)
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.network
public fun interface SocketEngine {
public fun create(host: String, port: Int): RawSocket
}
@@ -0,0 +1,13 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.network
public interface WriteChannel {
public suspend fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
public suspend fun flush()
}
@@ -7,20 +7,20 @@
package cn.rtast.libmc.packet
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public interface Encoder<in T> {
public suspend fun encode(buffer: BytesBuffer, value: T)
public fun encode(buffer: BytesBuffer, value: T)
}
public interface Decoder<out T> {
public suspend fun decode(buffer: BytesBuffer): T
public fun decode(buffer: BytesBuffer): T
}
public interface PacketCodec<T> : Encoder<T>, Decoder<T>
public suspend fun BytesBuffer.writeBuffer(source: BytesBuffer, length: Long = source.remaining) {
public fun BytesBuffer.writeBuffer(source: BytesBuffer, length: Int = source.size) {
if (length <= 0) return
val bytes = source.readBytes(length.toInt())
val bytes = source.readBytes(length)
this.writeBytes(bytes)
}
@@ -8,7 +8,7 @@
package cn.rtast.libmc.packet
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import kotlin.reflect.KClass
public class PacketRegistry {
@@ -26,10 +26,10 @@ public class PacketRegistry {
register(id, T::class, codec)
}
public suspend fun decodePacket(packetId: Int, buffer: BytesBuffer): MinecraftPacket =
idToCodec[packetId]?.decode(buffer) ?: ClientboundUnknownPacket(packetId, buffer.readRemainingBytes())
public fun decodePacket(packetId: Int, buffer: BytesBuffer): MinecraftPacket =
idToCodec[packetId]?.decode(buffer) ?: ClientboundUnknownPacket(packetId, buffer.toByteArray())
public suspend fun <T : MinecraftPacket> encodePacket(buffer: BytesBuffer, packet: T) {
public fun <T : MinecraftPacket> encodePacket(buffer: BytesBuffer, packet: T) {
@Suppress("UNCHECKED_CAST")
val info = requireNotNull(classToInfo[packet::class]) {
"Unregistered Packet ${packet::class.simpleName}"
@@ -7,16 +7,16 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public typealias BitSet = LongArray
public suspend fun BytesBuffer.readBitSet(): BitSet {
public fun BytesBuffer.readBitSet(): BitSet {
val count = this.readVarInt()
return LongArray(count) { this.readLong() }
}
public suspend fun BytesBuffer.writeBitSet(data: BitSet) {
public fun BytesBuffer.writeBitSet(data: BitSet) {
this.writeVarInt(data.size)
for (i in data.indices) this.writeLong(data[i])
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public sealed class Either<out L, out R> {
public data class Left<out L>(val value: L) : Either<L, Nothing>()
@@ -17,7 +17,7 @@ public sealed class Either<out L, out R> {
public val isRight: Boolean get() = this is Right
}
public suspend inline fun <L, R> BytesBuffer.readEither(
public inline fun <L, R> BytesBuffer.readEither(
readLeft: BytesBuffer.() -> L,
readRight: BytesBuffer.() -> R,
): Either<L, R> {
@@ -25,7 +25,7 @@ public suspend inline fun <L, R> BytesBuffer.readEither(
return if (isLeft) Either.Left(readLeft(this)) else Either.Right(readRight(this))
}
public suspend inline fun <L, R> BytesBuffer.writeEither(
public inline fun <L, R> BytesBuffer.writeEither(
either: Either<L, R>,
writeLeft: BytesBuffer.(L) -> Unit,
writeRight: BytesBuffer.(R) -> Unit,
@@ -7,14 +7,14 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public sealed interface IdOrX<out T> {
public data class Inline<T>(val value: T) : IdOrX<T>
public data class Reference(val registryId: Int) : IdOrX<Nothing>
}
public suspend inline fun <T> BytesBuffer.writeIdOrX(value: IdOrX<T>, writeX: BytesBuffer.(T) -> Unit) {
public inline fun <T> BytesBuffer.writeIdOrX(value: IdOrX<T>, writeX: BytesBuffer.(T) -> Unit) {
when (value) {
is IdOrX.Inline -> {
this.writeVarInt(0)
@@ -25,7 +25,7 @@ public suspend inline fun <T> BytesBuffer.writeIdOrX(value: IdOrX<T>, writeX: By
}
}
public suspend inline fun <T> BytesBuffer.readIdOrX(readX: BytesBuffer.() -> T): IdOrX<T> {
public inline fun <T> BytesBuffer.readIdOrX(readX: BytesBuffer.() -> T): IdOrX<T> {
val id = this.readVarInt()
return if (id == 0) IdOrX.Inline(this.readX()) else IdOrX.Reference(id - 1)
}
@@ -7,14 +7,14 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public sealed interface IdSet {
public data class Tag(val tagName: String) : IdSet
public data class Entries(val ids: List<Int>) : IdSet
}
public suspend fun BytesBuffer.readIdSet(): IdSet {
public fun BytesBuffer.readIdSet(): IdSet {
val type = this.readVarInt()
return if (type == 0) {
IdSet.Tag(tagName = this.readMcString())
@@ -26,7 +26,7 @@ public suspend fun BytesBuffer.readIdSet(): IdSet {
}
}
public suspend fun BytesBuffer.writeIdSet(idSet: IdSet) {
public fun BytesBuffer.writeIdSet(idSet: IdSet) {
when (idSet) {
is IdSet.Tag -> {
this.writeVarInt(0)
@@ -8,21 +8,21 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public object McStringCodec : PacketCodec<String> {
override suspend fun encode(buffer: BytesBuffer, value: String) {
override fun encode(buffer: BytesBuffer, value: String) {
val bytes = value.encodeToByteArray()
VarIntCodec.encode(buffer, bytes.size)
buffer.writeBytes(bytes)
}
override suspend fun decode(buffer: BytesBuffer): String {
override fun decode(buffer: BytesBuffer): String {
val length = VarIntCodec.decode(buffer)
val bytes = buffer.readBytes(length)
return bytes.decodeToString()
}
}
public suspend fun BytesBuffer.writeMcString(value: String): Unit = McStringCodec.encode(this, value)
public suspend fun BytesBuffer.readMcString(): String = McStringCodec.decode(this)
public fun BytesBuffer.writeMcString(value: String): Unit = McStringCodec.encode(this, value)
public fun BytesBuffer.readMcString(): String = McStringCodec.decode(this)
@@ -7,9 +7,9 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public suspend inline fun <T> BytesBuffer.readOptional(block: BytesBuffer.() -> T): T? {
public inline fun <T> BytesBuffer.readOptional(block: BytesBuffer.() -> T): T? {
val hasData = this.readBoolean()
return if (hasData) block.invoke(this) else null
}
@@ -17,7 +17,7 @@ public suspend inline fun <T> BytesBuffer.readOptional(block: BytesBuffer.() ->
/**
* buffer.writeOptional(value.someValue) { writeBlockPos(it) }
*/
public suspend inline fun <T> BytesBuffer.writeOptional(value: T?, block: BytesBuffer.(T) -> Unit) {
public inline fun <T> BytesBuffer.writeOptional(value: T?, block: BytesBuffer.(T) -> Unit) {
if (value != null) {
this.writeBoolean(true)
block.invoke(this, value)
@@ -7,21 +7,21 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public suspend fun BytesBuffer.readPrefixedByteArray(): ByteArray {
public fun BytesBuffer.readPrefixedByteArray(): ByteArray {
val length = this.readVarInt()
val data = this.readBytes(length)
return data
}
public suspend fun BytesBuffer.writePrefixedByteArray(data: ByteArray) {
public fun BytesBuffer.writePrefixedByteArray(data: ByteArray) {
this.writeVarInt(data.size)
this.writeBytes(data)
}
public suspend fun BytesBuffer.writeOptionalPrefixedByteArray(data: ByteArray?) {
public fun BytesBuffer.writeOptionalPrefixedByteArray(data: ByteArray?) {
if (data != null) {
this.writeBoolean(true)
this.writeVarInt(data.size)
@@ -29,21 +29,21 @@ public suspend fun BytesBuffer.writeOptionalPrefixedByteArray(data: ByteArray?)
} else this.writeBoolean(false)
}
public suspend fun BytesBuffer.readPrefixedStringArray(): List<String> {
public fun BytesBuffer.readPrefixedStringArray(): List<String> {
val length = readVarInt()
val list = ArrayList<String>(length)
repeat(length) { list.add(readMcString()) }
return list
}
public suspend fun BytesBuffer.writePrefixedStringArray(value: List<String>) {
public fun BytesBuffer.writePrefixedStringArray(value: List<String>) {
writeVarInt(value.size)
for (item in value) writeMcString(item)
}
public suspend inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): List<T> {
public inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): List<T> {
val count = this.readVarInt()
require(count in 0..4096) {
"Prefixed array count $count is invalid (expected 0..4096). " +
@@ -54,7 +54,7 @@ public suspend inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() ->
return list
}
public suspend inline fun <T> BytesBuffer.writePrefixed(list: List<T>, writer: BytesBuffer.(T) -> Unit) {
public inline fun <T> BytesBuffer.writePrefixed(list: List<T>, writer: BytesBuffer.(T) -> Unit) {
this.writeVarInt(list.size)
for (item in list) this.writer(item)
}
@@ -7,15 +7,15 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import kotlin.uuid.Uuid
public suspend fun BytesBuffer.writeUuid(uuid: Uuid): Unit = uuid.toLongs { mostSignificantBits, leastSignificantBits ->
public fun BytesBuffer.writeUuid(uuid: Uuid): Unit = uuid.toLongs { mostSignificantBits, leastSignificantBits ->
this.writeLong(mostSignificantBits)
this.writeLong(leastSignificantBits)
}
public suspend fun BytesBuffer.readUuid(): Uuid {
public fun BytesBuffer.readUuid(): Uuid {
val most = this.readLong()
val least = this.readLong()
return Uuid.fromLongs(most, least)
@@ -8,10 +8,11 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.network.ReadChannel
public object VarIntCodec : PacketCodec<Int> {
override suspend fun encode(buffer: BytesBuffer, value: Int) {
override fun encode(buffer: BytesBuffer, value: Int) {
var v = value
while (true) {
if ((v and 0x7F.inv()) == 0) {
@@ -23,7 +24,7 @@ public object VarIntCodec : PacketCodec<Int> {
}
}
override suspend fun decode(buffer: BytesBuffer): Int {
override fun decode(buffer: BytesBuffer): Int {
var numRead = 0
var result = 0
var read: Byte
@@ -38,8 +39,22 @@ public object VarIntCodec : PacketCodec<Int> {
}
}
public suspend fun BytesBuffer.writeVarInt(value: Int): Unit = VarIntCodec.encode(this, value)
public suspend fun BytesBuffer.readVarInt(): Int = VarIntCodec.decode(this)
public fun BytesBuffer.writeVarInt(value: Int): Unit = VarIntCodec.encode(this, value)
public fun BytesBuffer.readVarInt(): Int = VarIntCodec.decode(this)
public suspend fun BytesBuffer.writeVarLong(value: Long): Unit = VarLongCodec.encode(this, value)
public suspend fun BytesBuffer.readVarLong(): Long = VarLongCodec.decode(this)
public fun BytesBuffer.writeVarLong(value: Long): Unit = VarLongCodec.encode(this, value)
public fun BytesBuffer.readVarLong(): Long = VarLongCodec.decode(this)
public suspend fun ReadChannel.readVarInt(): Int {
var numRead = 0
var result = 0
var read: Byte
do {
read = readByte()
val value = (read.toInt() and 0x7F)
result = result or (value shl (7 * numRead))
numRead++
if (numRead > 5) throw IllegalArgumentException("VarInt is too big")
} while ((read.toInt() and 0x80) != 0)
return result
}
@@ -8,10 +8,10 @@
package cn.rtast.libmc.primitives
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public object VarLongCodec : PacketCodec<Long> {
override suspend fun encode(buffer: BytesBuffer, value: Long) {
override fun encode(buffer: BytesBuffer, value: Long) {
var v = value
while (true) {
if ((v and 0x7FL.inv()) == 0L) {
@@ -23,7 +23,7 @@ public object VarLongCodec : PacketCodec<Long> {
}
}
override suspend fun decode(buffer: BytesBuffer): Long {
override fun decode(buffer: BytesBuffer): Long {
var numRead = 0
var result = 0L
var read: Byte
@@ -1,42 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
public expect class BytesBuffer {
public constructor()
public constructor(bytes: ByteArray)
public suspend fun writeByte(value: Byte)
public suspend fun writeShort(value: Short, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public suspend fun writeInt(value: Int, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public suspend fun writeLong(value: Long, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public suspend fun writeDouble(value: Double, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public suspend fun writeFloat(value: Float, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public suspend fun writeBytes(bytes: ByteArray)
public suspend fun writeBoolean(value: Boolean)
public suspend fun readByte(): Byte
public suspend fun readUByte(): UByte
public suspend fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
public suspend fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
public suspend fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
public suspend fun readDouble(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Double
public suspend fun readFloat(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Float
public suspend fun readBytes(length: Int): ByteArray
public suspend fun readBoolean(): Boolean
public suspend fun readRemainingBytes(): ByteArray
public suspend fun toByteArray(): ByteArray
public suspend fun hasRemaining(): Boolean
public suspend fun close()
public val size: Int
public val remaining: Long
}
@Suppress("NOTHING_TO_INLINE")
public inline fun ByteArray.wrap(): BytesBuffer = BytesBuffer(this)
@@ -1,28 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
/**
* Platform specified raw byte read channel
*/
public expect open class ReadChannel() {
public open suspend fun readByte(): Byte
public open suspend fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
public open suspend fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
public open suspend fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
public open suspend fun readBytes(length: Int): ByteArray
public open suspend fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
}
/**
* Platform specified raw byte write channel
*/
public expect open class WriteChannel() {
public open suspend fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
public open suspend fun flush()
}
@@ -1,22 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import cn.rtast.libmc.LibMCContext
public expect class Socket public constructor(host: String, port: Int, context: LibMCContext) {
public fun openReadChannel(): ReadChannel
public fun openWriteChannel(): WriteChannel
public fun close()
}
public expect class UdpSocket public constructor(host: String, port: Int, context: LibMCContext) {
public suspend fun sendAndReceive(data: ByteArray): ByteArray
public fun close()
}
@@ -1,12 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc
/**
* No Context for jvm targets
*/
public actual class LibMCContext
@@ -1,132 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
public actual class BytesBuffer {
private val outStream = ByteArrayOutputStream()
private var readBuffer: ByteArray? = null
private var readOffset = 0
public actual constructor()
public actual constructor(bytes: ByteArray) {
this.readBuffer = bytes
outStream.write(bytes)
}
public actual suspend fun writeByte(value: Byte) {
readBuffer = null
outStream.write(value.toInt())
}
public actual suspend fun writeShort(value: Short, endian: ByteOrder) {
readBuffer = null
val v = value.toInt()
if (endian == ByteOrder.BIG_ENDIAN) {
outStream.write(v shr 8)
outStream.write(v)
} else {
outStream.write(v)
outStream.write(v shr 8)
}
}
public actual suspend fun writeInt(value: Int, endian: ByteOrder) {
readBuffer = null
if (endian == ByteOrder.BIG_ENDIAN) {
outStream.write(value shr 24)
outStream.write(value shr 16)
outStream.write(value shr 8)
outStream.write(value)
} else {
outStream.write(value)
outStream.write(value shr 8)
outStream.write(value shr 16)
outStream.write(value shr 24)
}
}
public actual suspend fun writeLong(value: Long, endian: ByteOrder) {
readBuffer = null
if (endian == ByteOrder.BIG_ENDIAN) {
for (i in 56 downTo 0 step 8) outStream.write((value shr i).toInt())
} else {
for (i in 0..56 step 8) outStream.write((value shr i).toInt())
}
}
public actual suspend fun writeDouble(value: Double, endian: ByteOrder) {
writeLong(value.toRawBits(), endian)
}
public actual suspend fun writeFloat(value: Float, endian: ByteOrder) {
writeInt(value.toRawBits(), endian)
}
public actual suspend fun writeBytes(bytes: ByteArray) {
readBuffer = null
withContext(Dispatchers.IO) { outStream.write(bytes) }
}
public actual suspend fun writeBoolean(value: Boolean): Unit = writeByte(if (value) 0x01 else 0x00)
private suspend fun ensureReadArray(): ByteArray {
var buf = readBuffer
if (buf == null) {
buf = outStream.toByteArray()
readBuffer = buf
}
return buf
}
public actual suspend fun readByte(): Byte {
val array = ensureReadArray()
if (readOffset >= array.size) throw IndexOutOfBoundsException("Buffer underflow")
return array[readOffset++]
}
public actual suspend fun readUByte(): UByte = this.readByte().toUByte()
public actual suspend fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
public actual suspend fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
public actual suspend fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
public actual suspend fun readDouble(endian: ByteOrder): Double {
return Double.fromBits(readLong(endian))
}
public actual suspend fun readFloat(endian: ByteOrder): Float {
return Float.fromBits(readInt(endian))
}
public actual suspend fun readBytes(length: Int): ByteArray {
val array = ensureReadArray()
if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow")
val result = array.copyOfRange(readOffset, readOffset + length)
readOffset += length
return result
}
public actual suspend fun readBoolean(): Boolean = this.readByte() != 0x00.toByte()
public actual suspend fun readRemainingBytes(): ByteArray {
val array = ensureReadArray()
if (readOffset >= array.size) return byteArrayOf()
val result = array.copyOfRange(readOffset, array.size)
readOffset = array.size
return result
}
public actual suspend fun toByteArray(): ByteArray = outStream.toByteArray()
public actual suspend fun hasRemaining(): Boolean = readOffset < ensureReadArray().size
public actual suspend fun close(): Unit = withContext(Dispatchers.IO) { outStream.close() }
public actual val size: Int get() = outStream.size()
public actual val remaining: Long get() = (outStream.size() - readOffset).coerceAtLeast(0).toLong()
}
@@ -1,58 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import java.io.EOFException
import java.io.InputStream
import java.io.OutputStream
public actual open class ReadChannel public actual constructor() {
private lateinit var _inputStream: InputStream
public constructor(inputStream: InputStream) : this() {
this._inputStream = inputStream
}
public actual open suspend fun readFully(out: ByteArray, start: Int, end: Int) {
var bytesRead = 0
val length = end - start
while (bytesRead < length) {
val read = _inputStream.read(out, start + bytesRead, length - bytesRead)
if (read == -1) throw EOFException("End of stream reached")
bytesRead += read
}
}
public actual open suspend fun readByte(): Byte {
val buf = ByteArray(1)
readFully(buf, 0, 1)
return buf[0]
}
public actual open suspend fun readBytes(length: Int): ByteArray {
val bytes = ByteArray(length)
readFully(bytes, 0, length)
return bytes
}
public actual open suspend fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
public actual open suspend fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
public actual open suspend fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
}
public actual open class WriteChannel public actual constructor() {
private lateinit var _outputStream: OutputStream
public constructor(outputStream: OutputStream) : this() {
_outputStream = outputStream
}
public actual open suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int): Unit =
_outputStream.write(value, startIndex, endIndex - startIndex)
public actual open suspend fun flush(): Unit = _outputStream.flush()
}
@@ -1,40 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.libmc.stream
internal fun ByteArray.toShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short {
val b1 = this[0].toInt() and 0xFF
val b2 = this[1].toInt() and 0xFF
return if (endian == ByteOrder.BIG_ENDIAN) {
((b1 shl 8) or b2).toShort()
} else {
((b2 shl 8) or b1).toShort()
}
}
internal fun ByteArray.toInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int {
val b1 = this[0].toInt() and 0xFF
val b2 = this[1].toInt() and 0xFF
val b3 = this[2].toInt() and 0xFF
val b4 = this[3].toInt() and 0xFF
return if (endian == ByteOrder.BIG_ENDIAN) {
(b1 shl 24) or (b2 shl 16) or (b3 shl 8) or b4
} else {
(b4 shl 24) or (b3 shl 16) or (b2 shl 8) or b1
}
}
internal fun ByteArray.toLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long {
var result = 0L
if (endian == ByteOrder.BIG_ENDIAN) {
for (b in this) result = (result shl 8) or (b.toLong() and 0xFF)
} else {
for (i in 7 downTo 0) result = (result shl 8) or (this[i].toLong() and 0xFF)
}
return result
}
@@ -1,40 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import cn.rtast.libmc.LibMCContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.net.Socket as JvmSocket
public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val socket = JvmSocket(host, port)
public actual fun openReadChannel(): ReadChannel = ReadChannel(socket.getInputStream())
public actual fun openWriteChannel(): WriteChannel = WriteChannel(socket.getOutputStream())
public actual fun close(): Unit = socket.close()
}
public actual class UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val socket = DatagramSocket().apply {
soTimeout = 3000
connect(InetSocketAddress(host, port))
}
public actual suspend fun sendAndReceive(data: ByteArray): ByteArray = withContext(Dispatchers.IO) {
socket.send(DatagramPacket(data, data.size))
val buf = ByteArray(2048)
val receivePacket = DatagramPacket(buf, buf.size)
socket.receive(receivePacket)
return@withContext buf.copyOf(receivePacket.length)
}
public actual fun close(): Unit = socket.close()
}
@@ -1,23 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
@file:Suppress("PropertyName")
package cn.rtast.libmc
import io.ktor.network.selector.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
public actual class LibMCContext actual constructor() {
internal var _selectorManager: SelectorManager = SelectorManager(Dispatchers.IO)
internal var _autoCloseSelectorManager: Boolean = false
public constructor(selectorManager: SelectorManager, autoClose: Boolean = false) : this() {
_selectorManager = selectorManager
_autoCloseSelectorManager = autoClose
}
}
@@ -1,84 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import io.ktor.utils.io.core.*
import kotlinx.io.*
import kotlinx.io.Buffer
public actual class BytesBuffer {
private val _delegateBuf: Buffer
public actual constructor() {
_delegateBuf = Buffer()
}
public actual constructor(bytes: ByteArray) {
_delegateBuf = Buffer().apply { write(bytes) }
}
public actual suspend fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value)
public actual suspend fun writeShort(value: Short, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeShort(value)
else _delegateBuf.writeShortLe(value)
}
public actual suspend fun writeInt(value: Int, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeInt(value)
else _delegateBuf.writeIntLe(value)
}
public actual suspend fun writeLong(value: Long, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeLong(value)
else _delegateBuf.writeLongLe(value)
}
public actual suspend fun writeDouble(value: Double, endian: ByteOrder): Unit =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeDouble(value) else _delegateBuf.writeDoubleLe(value)
public actual suspend fun writeFloat(value: Float, endian: ByteOrder): Unit =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeFloat(value) else _delegateBuf.writeFloatLe(value)
public actual suspend fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes)
public actual suspend fun writeBoolean(value: Boolean): Unit = _delegateBuf.writeByte(if (value) 0x01 else 0x00)
public actual suspend fun readByte(): Byte = _delegateBuf.readByte()
public actual suspend fun readUByte(): UByte = this.readByte().toUByte()
public actual suspend fun readShort(endian: ByteOrder): Short =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readShort() else _delegateBuf.readShortLe()
public actual suspend fun readInt(endian: ByteOrder): Int =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readInt() else _delegateBuf.readIntLe()
public actual suspend fun readLong(endian: ByteOrder): Long =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readLong() else _delegateBuf.readLongLe()
public actual suspend fun readDouble(endian: ByteOrder): Double =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readDouble() else _delegateBuf.readDoubleLe()
public actual suspend fun readFloat(endian: ByteOrder): Float =
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readFloat() else _delegateBuf.readFloatLe()
public actual suspend fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
public actual suspend fun readBoolean(): Boolean = _delegateBuf.readByte() != 0x00.toByte()
public actual suspend fun readRemainingBytes(): ByteArray = this.toByteArray()
public actual suspend fun toByteArray(): ByteArray {
val copy = _delegateBuf.peek()
return try {
copy.readByteArray()
} finally {
copy.close()
}
}
public actual suspend fun hasRemaining(): Boolean = !_delegateBuf.exhausted()
public actual suspend fun close(): Unit = _delegateBuf.close()
public actual val size: Int get() = _delegateBuf.size.toInt()
public actual val remaining: Long get() = _delegateBuf.remaining
}
@@ -1,61 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import io.ktor.utils.io.*
import io.ktor.utils.io.bits.*
public actual open class ReadChannel public actual constructor() {
private lateinit var _readChannel: ByteReadChannel
public constructor(readChannel: ByteReadChannel) : this() {
this._readChannel = readChannel
}
public actual open suspend fun readByte(): Byte = _readChannel.readByte()
public actual open suspend fun readBytes(length: Int): ByteArray = _readChannel.readByteArray(length)
public actual open suspend fun readFully(out: ByteArray, start: Int, end: Int): Unit =
_readChannel.readFully(out, start, end)
public actual open suspend fun readShort(endian: ByteOrder): Short {
val bytes = readBytes(2)
val v = ((bytes[0].toInt() and 0xFF shl 8) or (bytes[1].toInt() and 0xFF)).toShort()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual open suspend fun readInt(endian: ByteOrder): Int {
val bytes = readBytes(4)
val v = (bytes[0].toInt() and 0xFF shl 24) or
(bytes[1].toInt() and 0xFF shl 16) or
(bytes[2].toInt() and 0xFF shl 8) or
(bytes[3].toInt() and 0xFF)
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual open suspend fun readLong(endian: ByteOrder): Long {
val bytes = readBytes(8)
var v = 0L
for (i in 0 until 8) {
v = (v shl 8) or (bytes[i].toLong() and 0xFF)
}
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
}
public actual open class WriteChannel public actual constructor() {
private lateinit var _writeChannel: ByteWriteChannel
public constructor(writeChannel: ByteWriteChannel) : this() {
_writeChannel = writeChannel
}
public actual open suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int): Unit =
_writeChannel.writeFully(value, startIndex, endIndex)
public actual open suspend fun flush(): Unit = _writeChannel.flush()
}
@@ -1,46 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.stream
import cn.rtast.libmc.LibMCContext
import io.ktor.network.sockets.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.runBlocking
import kotlinx.io.readByteArray
public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val ctx = context
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
public actual fun openReadChannel(): ReadChannel = ReadChannel(socket.openReadChannel())
public actual fun openWriteChannel(): WriteChannel =
WriteChannel(socket.openWriteChannel(autoFlush = true))
public actual fun close() {
socket.close()
if (ctx._autoCloseSelectorManager) ctx._selectorManager.close()
}
}
public actual class UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) {
private val ctx = context
// use bind to create an unconnected socket
private val socket = runBlocking { aSocket(ctx._selectorManager).udp().bind() }
private val remoteAddress = InetSocketAddress(host, port)
public actual suspend fun sendAndReceive(data: ByteArray): ByteArray {
val packet = buildPacket { writeFully(data) }
socket.send(Datagram(packet, remoteAddress))
return socket.receive().packet.readByteArray()
}
public actual fun close() {
socket.close()
if (ctx._autoCloseSelectorManager) ctx._selectorManager.close()
}
}
-23
View File
@@ -1,23 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
explicitApi()
withSourcesJar()
linuxX64()
linuxArm64()
macosArm64()
mingwX64()
jvm { compilerOptions.jvmTarget = JvmTarget.JVM_1_8 }
sourceSets {
commonMain.dependencies {
api(project(":common"))
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
}
}
}
@@ -1,110 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping.bedrock
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import kotlin.random.Random
private val RAKNET_MAGIC = byteArrayOf(
0x00, 0xFF.toByte(),
0xFF.toByte(), 0x00,
0xFE.toByte(), 0xFE.toByte(),
0xFE.toByte(), 0xFE.toByte(),
0xFD.toByte(), 0xFD.toByte(),
0xFD.toByte(), 0xFD.toByte(),
0x12, 0x34, 0x56, 0x78
)
internal interface MinecraftBedrockPacket {
val packetId: Byte
}
internal data class BedrockRequestPacket(
val time: Long,
val magic: ByteArray = RAKNET_MAGIC,
val guid: Long = Random.nextLong(),
) : MinecraftBedrockPacket {
override val packetId: Byte = 0x01
companion object Codec : PacketCodec<BedrockRequestPacket> {
override suspend fun encode(buffer: BytesBuffer, value: BedrockRequestPacket) {
buffer.writeByte(value.packetId)
buffer.writeLong(value.time)
buffer.writeBytes(value.magic)
buffer.writeLong(value.guid)
}
override suspend fun decode(buffer: BytesBuffer): BedrockRequestPacket = throw UnsupportedOperationException()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as BedrockRequestPacket
if (time != other.time) return false
if (guid != other.guid) return false
if (packetId != other.packetId) return false
if (!magic.contentEquals(other.magic)) return false
return true
}
override fun hashCode(): Int {
var result = time.hashCode()
result = 31 * result + guid.hashCode()
result = 31 * result + packetId
result = 31 * result + magic.contentHashCode()
return result
}
}
internal data class BedrockResponsePacket(
override val packetId: Byte,
val time: Long,
val serverGuid: Long,
val magic: ByteArray,
val payload: String,
) : MinecraftBedrockPacket {
companion object Codec : PacketCodec<BedrockResponsePacket> {
override suspend fun encode(buffer: BytesBuffer, value: BedrockResponsePacket) = throw UnsupportedOperationException()
override suspend fun decode(buffer: BytesBuffer): BedrockResponsePacket {
val packetId = buffer.readByte()
if (packetId != 0x1C.toByte()) throw IllegalStateException("Expected pong id 0x1C, got $packetId")
val time = buffer.readLong()
val serverGuid = buffer.readLong()
val magic = buffer.readBytes(16)
if (!magic.contentEquals(RAKNET_MAGIC)) throw IllegalStateException("Invalid magic in response")
val payloadLength = buffer.readShort().toInt() and 0xFFFF
val payloadBytes = buffer.readBytes(payloadLength)
val payload = payloadBytes.decodeToString()
return BedrockResponsePacket(packetId, time, serverGuid, magic, payload)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as BedrockResponsePacket
if (packetId != other.packetId) return false
if (time != other.time) return false
if (serverGuid != other.serverGuid) return false
if (!magic.contentEquals(other.magic)) return false
if (payload != other.payload) return false
return true
}
override fun hashCode(): Int {
var result = packetId.toInt()
result = 31 * result + time.hashCode()
result = 31 * result + serverGuid.hashCode()
result = 31 * result + magic.contentHashCode()
result = 31 * result + payload.hashCode()
return result
}
}
@@ -1,19 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping.bedrock
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.stream.UdpSocket
internal suspend fun <T : MinecraftBedrockPacket> UdpSocket.sendPacket(packet: T, codec: PacketCodec<T>): ByteArray {
val buf = BytesBuffer()
codec.encode(buf, packet)
return sendAndReceive(buf.toByteArray())
}
@@ -1,29 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping.bedrock
import cn.rtast.libmc.LibMCContext
import cn.rtast.libmc.stream.UdpSocket
import cn.rtast.libmc.stream.wrap
import cn.rtast.libmc.mcping.PingResponse
import kotlin.time.Clock
internal suspend fun pingBedrockServer(host: String, port: Int, context: LibMCContext): PingResponse {
val socket = UdpSocket(host, port, context)
return try {
val sendTime = Clock.System.now().toEpochMilliseconds()
val requestPacket = BedrockRequestPacket(sendTime)
val responseBytes = socket.sendPacket(requestPacket, BedrockRequestPacket)
val receiveTime = Clock.System.now().toEpochMilliseconds()
val responsePacket = BedrockResponsePacket.decode(responseBytes.wrap())
val latency = (receiveTime - sendTime).toInt()
PingResponse(responsePacket.payload, latency)
} finally {
socket.close()
}
}
@@ -1,116 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping.bedrock
import cn.rtast.libmc.mcping.PingResponse
public enum class BedrockGameMode(public val gameMode: String) {
Survival("Survival"),
Creative("Creative"),
Adventure("Adventure"),
Spectator("Spectator"), // reserved?
Hardcore("Hardcore"),
Unknown(""); // reserved
public companion object {
public fun parse(value: String?): BedrockGameMode {
return entries.firstOrNull {
it.name.equals(value, ignoreCase = true)
} ?: Unknown
}
}
}
public data class BedrockPingResponse(
/**
* always be `MCPE`
* index 0
*/
val protocolHeader: String,
/**
* MOTD line 1
* index 1
*/
val motdLine1: String,
/**
* protocol version
* index 2
*/
val protocolVersion: Int,
/**
* game version
* index 3
*/
val gameVersion: String,
/**
* online players
* index 4
*/
val onlinePlayers: Int,
/**
* maximum players
* index 5
*/
val maximumPlayers: Int,
/**
* server GUID
* parsed as String -> origin bytes count is 8
* index 6
*/
val serverGUID: String,
/**
* MOTD line 2
* index 7
*/
val motdLine2: String,
/**
* game mode
* index 8
*/
val gameMode: BedrockGameMode,
/**
* Nintendo Limited
* Nintendo Switch online restriction flag (1 indicates restriction enabled/processed)
* index 9
*/
val nintendoLimited: Boolean,
/**
* ipv4 port
* if disabled or unconfigured, it will be 0
* index 10
*/
val ipv4Port: Int,
/**
* ipv6 port
* if disabled or unconfigured, it will be 0
* index 11
*/
val ipv6Port: Int,
/**
* latency ms
* reserved not exists in response packet
*/
val latency: Int,
)
internal fun PingResponse.parseBedrockPingResponse(): BedrockPingResponse {
try {
val fields = content.split(";")
return BedrockPingResponse(
fields[0], fields[1], fields[2].toInt(),
fields[3], fields[4].toInt(),
fields[5].toInt(), fields[6],
fields[7], BedrockGameMode.parse(fields[8]),
fields.getOrNull(9) == "1", fields[10].toInt(),
fields[11].toInt(), this.latency // pass through PingResponse.latency
)
} catch (e: Exception) {
e.printStackTrace()
throw IllegalStateException("The server respond incorrect response: Missing fields or type mismatch")
}
}
@@ -1,31 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.mcping.java
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.stream.ReadChannel
import cn.rtast.libmc.stream.wrap
private suspend fun ReadChannel.readVarInt(): Int {
var numRead = 0
var result = 0
var read: Byte
do {
read = this.readByte()
val value = (read.toInt() and 0x7F)
result = result or (value shl (7 * numRead))
numRead++
if (numRead > 5) throw IllegalArgumentException("VarInt is too big")
} while ((read.toInt() and 0x80) != 0)
return result
}
public suspend fun ReadChannel.readPacketFrame(): BytesBuffer {
val length = this.readVarInt()
return this.readBytes(length).wrap()
}
@@ -1,52 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping.java
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.primitives.McStringCodec
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.VarIntCodec
import cn.rtast.libmc.packet.MinecraftPacket
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Handshake
internal data class HandshakePacket(
val protocolVersion: Int,
val serverAddress: String,
val serverPort: UShort,
// 1 -> Status, 2 -> Login
val nextState: Int,
) : MinecraftPacket {
companion object Codec : PacketCodec<HandshakePacket> {
override suspend fun encode(buffer: BytesBuffer, value: HandshakePacket) {
VarIntCodec.encode(buffer, value.protocolVersion)
McStringCodec.encode(buffer, value.serverAddress)
buffer.writeShort(value.serverPort.toShort())
VarIntCodec.encode(buffer, value.nextState)
}
override suspend fun decode(buffer: BytesBuffer): HandshakePacket = throw UnsupportedOperationException()
}
}
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Status
internal data object StatusRequestPacket : MinecraftPacket, PacketCodec<StatusRequestPacket> {
override suspend fun encode(buffer: BytesBuffer, value: StatusRequestPacket) {}
override suspend fun decode(buffer: BytesBuffer): StatusRequestPacket = throw UnsupportedOperationException()
}
internal data class PingPacket(val currentTime: Long) : MinecraftPacket {
companion object : PacketCodec<PingPacket> {
override suspend fun encode(buffer: BytesBuffer, value: PingPacket) {
buffer.writeLong(value.currentTime)
}
override suspend fun decode(buffer: BytesBuffer): PingPacket {
return PingPacket(buffer.readLong())
}
}
}
@@ -1,54 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping.java
import cn.rtast.libmc.LibMCContext
import cn.rtast.libmc.mcping.PingResponse
import cn.rtast.libmc.mcping.sendPacket
import cn.rtast.libmc.primitives.McStringCodec
import cn.rtast.libmc.primitives.VarIntCodec
import cn.rtast.libmc.stream.Socket
import kotlin.time.Clock
internal suspend fun pingJavaServer(host: String, port: Int, context: LibMCContext): PingResponse {
val socket = Socket(host, port, context)
val receiveChannel = socket.openReadChannel()
val sendChannel = socket.openWriteChannel()
return try {
val handshakePacket = HandshakePacket(
protocolVersion = -1,
serverAddress = host,
serverPort = port.toUShort(),
nextState = 1
)
sendChannel.sendPacket(handshakePacket, 0x00, HandshakePacket)
sendChannel.sendPacket(StatusRequestPacket, 0x00, StatusRequestPacket)
val statusFrameBuffer = receiveChannel.readPacketFrame()
val statusPacketId = VarIntCodec.decode(statusFrameBuffer)
if (statusPacketId != 0x00) {
throw IllegalStateException("Expected StatusResponse packet ID 0x00, got $statusPacketId")
}
val jsonResponse = McStringCodec.decode(statusFrameBuffer)
val sendTime = Clock.System.now().toEpochMilliseconds()
val pingPacket = PingPacket(sendTime)
sendChannel.sendPacket(pingPacket, 0x01, PingPacket)
val pongFrameBuffer = receiveChannel.readPacketFrame()
val pongPacketId = VarIntCodec.decode(pongFrameBuffer)
if (pongPacketId != 0x01) {
throw IllegalStateException("Expected Pong packet ID 0x01, got $pongPacketId")
}
val latency = (Clock.System.now().toEpochMilliseconds() - sendTime).toInt()
PingResponse(jsonResponse, latency)
} finally {
socket.close()
}
}
@@ -1,27 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
@file:JvmName("McPing")
package cn.rtast.libmc.mcping
import cn.rtast.libmc.LibMCContext
import cn.rtast.libmc.mcping.bedrock.pingBedrockServer
import cn.rtast.libmc.mcping.java.pingJavaServer
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
@JvmOverloads
public suspend fun mcping(
host: String,
port: Int,
type: ServerType = ServerType.Java,
context: LibMCContext = LibMCContext(),
): PingResponse = when (type) {
ServerType.Java -> pingJavaServer(host, port, context)
ServerType.Bedrock -> pingBedrockServer(host, port, context)
}
@@ -1,27 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.mcping
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.primitives.VarIntCodec
import cn.rtast.libmc.stream.WriteChannel
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.writeBuffer
public suspend fun <T : MinecraftPacket> WriteChannel.sendPacket(packet: T, packetId: Int, codec: PacketCodec<T>) {
val bodyBuffer = BytesBuffer()
VarIntCodec.encode(bodyBuffer, packetId)
codec.encode(bodyBuffer, packet)
val frameBuffer = BytesBuffer()
VarIntCodec.encode(bodyBuffer, bodyBuffer.size)
frameBuffer.writeBuffer(bodyBuffer)
val bytes = frameBuffer.toByteArray()
this.writeFully(bytes, 0, bytes.size)
this.flush()
}
@@ -1,24 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping
import cn.rtast.libmc.mcping.bedrock.BedrockPingResponse
import cn.rtast.libmc.mcping.bedrock.parseBedrockPingResponse
public data class PingResponse(
/**
* raw response content
*/
public val content: String,
/**
* latency ms
*/
public val latency: Int,
) {
public fun toBedrockResponse(): BedrockPingResponse = parseBedrockPingResponse()
}
@@ -1,12 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.libmc.mcping
public enum class ServerType {
Java, Bedrock
}
@@ -1,29 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package test
import cn.rtast.libmc.mcping.ServerType
import cn.rtast.libmc.mcping.mcping
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
class TestPing {
@Test
fun `test ping java server`() = runTest {
val response = mcping("org.mc-complex.com", 25565)
println(response)
}
@Test
fun `test ping bedrock server`() = runTest {
val response = mcping("play.wildnetwork.net", 19132, ServerType.Bedrock)
println(response)
println(response.toBedrockResponse())
}
}
@@ -1 +0,0 @@
MCPE;" &e&lWILD&f&lNETWORK &6&lS24 &8-&e discord.gg/WildPrison\n&a Release: &f531d 18h 19m 18s ago";2169;26.45;393;1500;11173189777763089377;Another Geyser server.;Survival;1;19132;0;
File diff suppressed because one or more lines are too long.
@@ -1,33 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package test;
import cn.rtast.libmc.mcping.McPing;
import cn.rtast.libmc.mcping.PingResponse;
import cn.rtast.libmc.mcping.ServerType;
import org.junit.Test;
public class TestPingInJava {
@Test
public void testPingJava() {
String testJavaHost = "org.mc-complex.com";
PingResponse resp = McPing.mcping(testJavaHost, 25565);
System.out.println(resp);
}
@Test
public void testPingBedrock() {
String testBedrockHost = "play.wildnetwork.net";
PingResponse resp = McPing.mcping(testBedrockHost, 19132, ServerType.Bedrock);
System.out.println(resp.getContent());
System.out.println(resp.getLatency());
System.out.println(resp.toBedrockResponse());
}
}
@@ -1,37 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package test
import cn.rtast.libmc.common.LibMCContext
import cn.rtast.libmc.mcping.ServerType
import cn.rtast.libmc.mcping.mcping
import io.ktor.network.selector.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
class TestMingwPing {
@Test
fun `test ping java on mingw with selectorManager`() = runTest {
val sm = SelectorManager(Dispatchers.IO)
val response =
mcping(host = "org.mc-complex.com", port = 25565, type = ServerType.Java, context = LibMCContext(sm))
println(response)
}
@Test
fun `test ping bedrock on mingw with selectorManager`() = runTest {
val sm = SelectorManager(Dispatchers.IO)
val response =
mcping(host = "play.wildnetwork.net", port = 19132, type = ServerType.Bedrock, context = LibMCContext(sm))
println(response)
println(response.toBedrockResponse())
}
}
@@ -7,12 +7,12 @@
package cn.rtast.libmc.nbt
import cn.rtast.libmc.stream.ByteOrder
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.ByteOrder
import cn.rtast.libmc.network.BytesBuffer
public class BytesBufferNBTInput(override val order: ByteOrder, private val buffer: BytesBuffer) : NBTInput {
override suspend fun readByte(): Byte = buffer.readByte()
override suspend fun readBytes(count: Int): ByteArray = buffer.readBytes(count)
override fun readByte(): Byte = buffer.readByte()
override fun readBytes(count: Int): ByteArray = buffer.readBytes(count)
}
public fun BytesBuffer.toNBTInput(order: ByteOrder = ByteOrder.BIG_ENDIAN): BytesBufferNBTInput =
@@ -7,8 +7,8 @@
package cn.rtast.libmc.nbt
import cn.rtast.libmc.stream.ByteOrder
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.ByteOrder
import cn.rtast.libmc.network.BytesBuffer
public class BytesBufferNBTOutput(
@@ -16,9 +16,9 @@ public class BytesBufferNBTOutput(
override val root: NBTTag.CompoundTag,
private val buffer: BytesBuffer,
) : NBTOutput {
override suspend fun writeByte(value: Byte): Unit = buffer.writeByte(value)
override suspend fun writeBytes(value: ByteArray): Unit = buffer.writeBytes(value)
override suspend fun toByteArray(): ByteArray = buffer.toByteArray()
override fun writeByte(value: Byte): Unit = buffer.writeByte(value)
override fun writeBytes(value: ByteArray): Unit = buffer.writeBytes(value)
override fun toByteArray(): ByteArray = buffer.toByteArray()
}
public fun BytesBuffer.toNBTOutput(
@@ -7,33 +7,33 @@
package cn.rtast.libmc.nbt
import cn.rtast.libmc.stream.ByteOrder
import cn.rtast.libmc.network.ByteOrder
public interface NBTInput {
public val order: ByteOrder
public suspend fun readByte(): Byte
public suspend fun readBytes(count: Int): ByteArray
public fun readByte(): Byte
public fun readBytes(count: Int): ByteArray
public suspend fun readLong(): Long = if (order == ByteOrder.BIG_ENDIAN) readLongBE() else readLongLE()
public suspend fun readFloat(): Float = if (order == ByteOrder.BIG_ENDIAN) readFloatBE() else readFloatLE()
public suspend fun readDouble(): Double = if (order == ByteOrder.BIG_ENDIAN) readDoubleBE() else readDoubleLE()
public suspend fun readShort(): Short = if (order == ByteOrder.BIG_ENDIAN) readShortBE() else readShortLE()
public suspend fun readInt(): Int = if (order == ByteOrder.BIG_ENDIAN) readIntBE() else readIntLE()
public fun readLong(): Long = if (order == ByteOrder.BIG_ENDIAN) readLongBE() else readLongLE()
public fun readFloat(): Float = if (order == ByteOrder.BIG_ENDIAN) readFloatBE() else readFloatLE()
public fun readDouble(): Double = if (order == ByteOrder.BIG_ENDIAN) readDoubleBE() else readDoubleLE()
public fun readShort(): Short = if (order == ByteOrder.BIG_ENDIAN) readShortBE() else readShortLE()
public fun readInt(): Int = if (order == ByteOrder.BIG_ENDIAN) readIntBE() else readIntLE()
// big endian
public suspend fun readLongBE(): Long =
public fun readLongBE(): Long =
(readIntBE().toLong() shl 32) or (readIntBE().toLong() and 0xFFFFFFFFL)
public suspend fun readFloatBE(): Float = Float.fromBits(readIntBE())
public suspend fun readDoubleBE(): Double = Double.fromBits(readLongBE())
public suspend fun readShortBE(): Short {
public fun readFloatBE(): Float = Float.fromBits(readIntBE())
public fun readDoubleBE(): Double = Double.fromBits(readLongBE())
public fun readShortBE(): Short {
val b1 = readByte().toInt() and 0xFF
val b2 = readByte().toInt() and 0xFF
return ((b1 shl 8) or b2).toShort()
}
public suspend fun readIntBE(): Int {
public fun readIntBE(): Int {
return ((readByte().toInt() and 0xFF) shl 24) or
((readByte().toInt() and 0xFF) shl 16) or
((readByte().toInt() and 0xFF) shl 8) or
@@ -41,7 +41,7 @@ public interface NBTInput {
}
// little endian
public suspend fun readLongLE(): Long {
public fun readLongLE(): Long {
return ((readByte().toInt() and 0xFF).toLong() shl 0) or
((readByte().toInt() and 0xFF).toLong() shl 8) or
((readByte().toInt() and 0xFF).toLong() shl 16) or
@@ -52,14 +52,14 @@ public interface NBTInput {
(readByte().toInt() and 0xFF).toLong()
}
public suspend fun readFloatLE(): Float = Float.fromBits(readIntLE())
public fun readFloatLE(): Float = Float.fromBits(readIntLE())
public suspend fun readDoubleLE(): Double = Double.fromBits(readLongLE())
public fun readDoubleLE(): Double = Double.fromBits(readLongLE())
public suspend fun readShortLE(): Short =
public fun readShortLE(): Short =
((readByte().toInt() and 0xFF) or (readByte().toInt() and 0xFF shl 8)).toShort()
public suspend fun readIntLE(): Int =
public fun readIntLE(): Int =
((readByte().toInt() and 0xFF)) or
((readByte().toInt() and 0xFF) shl 8) or
((readByte().toInt() and 0xFF) shl 16) or
@@ -7,44 +7,44 @@
package cn.rtast.libmc.nbt
import cn.rtast.libmc.stream.ByteOrder
import cn.rtast.libmc.network.ByteOrder
public interface NBTOutput {
public val order: ByteOrder
public val root: NBTTag.CompoundTag
public suspend fun writeByte(value: Byte)
public suspend fun writeBytes(value: ByteArray)
public fun writeByte(value: Byte)
public fun writeBytes(value: ByteArray)
public suspend fun writeShort(value: Short): Unit =
public fun writeShort(value: Short): Unit =
if (order == ByteOrder.BIG_ENDIAN) writeShortBE(value) else writeShortLE(value)
public suspend fun writeInt(value: Int): Unit =
public fun writeInt(value: Int): Unit =
if (order == ByteOrder.BIG_ENDIAN) writeIntBE(value) else writeIntLE(value)
public suspend fun writeLong(value: Long): Unit =
public fun writeLong(value: Long): Unit =
if (order == ByteOrder.BIG_ENDIAN) writeLongBE(value) else writeLongLE(value)
public suspend fun writeFloat(value: Float): Unit =
public fun writeFloat(value: Float): Unit =
if (order == ByteOrder.BIG_ENDIAN) writeFloatBE(value) else writeFloatLE(value)
public suspend fun writeDouble(value: Double): Unit =
public fun writeDouble(value: Double): Unit =
if (order == ByteOrder.BIG_ENDIAN) writeDoubleBE(value) else writeDoubleLE(value)
// big endian
public suspend fun writeShortBE(value: Short) {
public fun writeShortBE(value: Short) {
writeByte(((value.toInt() shr 8) and 0xFF).toByte())
writeByte((value.toInt() and 0xFF).toByte())
}
public suspend fun writeIntBE(value: Int) {
public fun writeIntBE(value: Int) {
writeByte(((value shr 24) and 0xFF).toByte())
writeByte(((value shr 16) and 0xFF).toByte())
writeByte(((value shr 8) and 0xFF).toByte())
writeByte((value and 0xFF).toByte())
}
public suspend fun writeLongBE(value: Long) {
public fun writeLongBE(value: Long) {
writeByte(((value shr 56).toInt() and 0xFF).toByte())
writeByte(((value shr 48).toInt() and 0xFF).toByte())
writeByte(((value shr 40).toInt() and 0xFF).toByte())
@@ -55,28 +55,28 @@ public interface NBTOutput {
writeByte((value.toInt() and 0xFF).toByte())
}
public suspend fun writeFloatBE(value: Float) {
public fun writeFloatBE(value: Float) {
writeIntBE(value.toBits())
}
public suspend fun writeDoubleBE(value: Double) {
public fun writeDoubleBE(value: Double) {
writeLongBE(value.toBits())
}
// Little Endian
public suspend fun writeShortLE(value: Short) {
public fun writeShortLE(value: Short) {
writeByte((value.toInt() and 0xFF).toByte())
writeByte(((value.toInt() shr 8) and 0xFF).toByte())
}
public suspend fun writeIntLE(value: Int) {
public fun writeIntLE(value: Int) {
writeByte((value and 0xFF).toByte())
writeByte(((value shr 8) and 0xFF).toByte())
writeByte(((value shr 16) and 0xFF).toByte())
writeByte(((value shr 24) and 0xFF).toByte())
}
public suspend fun writeLongLE(value: Long) {
public fun writeLongLE(value: Long) {
writeByte(((value and 0xFF).toInt()).toByte())
writeByte((((value shr 8) and 0xFF).toInt()).toByte())
writeByte((((value shr 16) and 0xFF).toInt()).toByte())
@@ -87,7 +87,7 @@ public interface NBTOutput {
writeByte((((value shr 56) and 0xFF).toInt()).toByte())
}
public suspend fun writeFloatLE(value: Float): Unit = writeIntLE(value.toBits())
public suspend fun writeDoubleLE(value: Double): Unit = writeLongLE(value.toBits())
public suspend fun toByteArray(): ByteArray
public fun writeFloatLE(value: Float): Unit = writeIntLE(value.toBits())
public fun writeDoubleLE(value: Double): Unit = writeLongLE(value.toBits())
public fun toByteArray(): ByteArray
}
@@ -7,13 +7,13 @@
package cn.rtast.libmc.nbt
public suspend fun NBTInput.readStringTag(): String {
public fun NBTInput.readStringTag(): String {
val length = readShort().toInt() and 0xFFFF
val bytes = readBytes(length)
return bytes.decodeToString()
}
public suspend fun NBTInput.readListTag(): NBTTag.ListTag {
public fun NBTInput.readListTag(): NBTTag.ListTag {
val elementTypeId = readByte().toInt() and 0xFF
val elementType = NBTType.fromID(elementTypeId)
val length = readInt()
@@ -22,7 +22,7 @@ public suspend fun NBTInput.readListTag(): NBTTag.ListTag {
return NBTTag.ListTag(elementType, list)
}
public suspend fun NBTInput.readCompoundTag(): NBTTag.CompoundTag {
public fun NBTInput.readCompoundTag(): NBTTag.CompoundTag {
val map = LinkedHashMap<String, NBTTag>()
while (true) {
val typeId = readByte().toInt() and 0xFF
@@ -35,7 +35,7 @@ public suspend fun NBTInput.readCompoundTag(): NBTTag.CompoundTag {
return NBTTag.CompoundTag(map)
}
public suspend fun NBTInput.readTagPayload(type: NBTType): NBTTag =
public fun NBTInput.readTagPayload(type: NBTType): NBTTag =
when (type) {
NBTType.Byte -> NBTTag.ByteTag(readByte())
NBTType.Short -> NBTTag.ShortTag(readShort())
@@ -52,7 +52,7 @@ public suspend fun NBTInput.readTagPayload(type: NBTType): NBTTag =
NBTType.End -> error("TAG_End no payload")
}
public suspend fun NBTInput.readCompound(): NBTTag {
public fun NBTInput.readCompound(): NBTTag {
val map = LinkedHashMap<String, NBTTag>()
while (true) {
val typeId = readByte().toInt()
@@ -67,7 +67,7 @@ public suspend fun NBTInput.readCompound(): NBTTag {
return NBTTag.ListTag(NBTType.Compound, map.values.toMutableList())
}
public suspend fun NBTInput.readRootCompound(): NBTCompound {
public fun NBTInput.readRootCompound(): NBTCompound {
val rootType = NBTType.fromID(readByte().toInt())
require(rootType == NBTType.Compound) { "Root tag must be TAG_Compound" }
val nameLen = readShort().toInt() and 0xFFFF
@@ -76,7 +76,7 @@ public suspend fun NBTInput.readRootCompound(): NBTCompound {
return NBTCompound(name, root)
}
public suspend fun NBTInput.readNetworkCompound(): NBTCompound {
public fun NBTInput.readNetworkCompound(): NBTCompound {
return when (val type = NBTType.fromID(readByte().toInt() and 0xFF)) {
NBTType.Compound -> NBTCompound("", readCompoundTag())
NBTType.String -> NBTCompound("", NBTTag.CompoundTag(linkedMapOf("text" to NBTTag.StringTag(readStringTag()))))
@@ -8,13 +8,13 @@
package cn.rtast.libmc.nbt
public suspend fun NBTOutput.writeStringTag(value: String) {
public fun NBTOutput.writeStringTag(value: String) {
val bytes = value.encodeToByteArray()
writeShort(bytes.size.toShort())
writeBytes(bytes)
}
public suspend fun NBTOutput.writeTagPayload(tag: NBTTag) {
public fun NBTOutput.writeTagPayload(tag: NBTTag) {
when (tag) {
is NBTTag.ByteTag -> writeByte(tag.value)
is NBTTag.ShortTag -> writeShort(tag.value)
@@ -56,14 +56,14 @@ public suspend fun NBTOutput.writeTagPayload(tag: NBTTag) {
}
}
public suspend fun NBTOutput.writeRootNBTCompound(name: String = ""): ByteArray {
public fun NBTOutput.writeRootNBTCompound(name: String = ""): ByteArray {
writeByte(NBTType.Compound.id)
writeStringTag(name)
writeTagPayload(root)
return toByteArray()
}
public suspend fun NBTOutput.writeNetworkCompound(compound: NBTCompound) {
public fun NBTOutput.writeNetworkCompound(compound: NBTCompound) {
val compoundTag = compound.element as? NBTTag.CompoundTag
?: throw IllegalArgumentException("NBTCompound element must be an NBTTag.CompoundTag")
writeByte(NBTType.Compound.id)
@@ -7,7 +7,7 @@
package test
import cn.rtast.libmc.stream.wrap
import cn.rtast.libmc.network.wrap
import cn.rtast.libmc.nbt.NbtReader
import org.junit.Test
import java.io.File
File renamed without changes.
@@ -16,6 +16,7 @@ kotlin {
implementation(libs.cryptography.core)
implementation(libs.cryptography.provider.optimal)
implementation(libs.ktor.client.core)
implementation(libs.ktor.network)
}
jvmTest.dependencies {
@@ -22,14 +22,14 @@ public class AesCFB8Cipher(sharedKey: ByteArray) : NetworkCipher {
.decodeFromByteArrayBlocking(AES.Key.Format.RAW, sharedKey)
.cipher()
override suspend fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
override fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
val plaintext = buffer.copyOfRange(offset, offset + length)
val ciphertext = cipher.encryptWithIvBlocking(encryptIv, plaintext)
ciphertext.copyInto(buffer, destinationOffset = offset)
updateIv(encryptIv, ciphertext)
}
override suspend fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
override fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
val ciphertext = buffer.copyOfRange(offset, offset + length)
val plaintext = cipher.decryptWithIvBlocking(decryptIv, ciphertext)
plaintext.copyInto(buffer, destinationOffset = offset)
@@ -28,4 +28,5 @@ public val DefaultProtocolContext: ProtocolContextBuilder.() -> Unit = {
}.status
require(status == HttpStatusCode.NoContent)
}
socketEngine = KtorNetworkEngine()
}
@@ -0,0 +1,50 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.SocketEngine
import cn.rtast.libmc.network.WriteChannel
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.utils.io.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
public class KtorNetworkEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = KtorNetworkSocket(host, port)
}
public class KtorNetworkSocket(private val host: String, private val port: Int) : RawSocket {
private val sm = SelectorManager(Dispatchers.IO)
private lateinit var socket: Socket
override suspend fun connect(): Unit = run { socket = aSocket(sm).tcp().connect(host, port) }
override fun openReadChannel(): ReadChannel = KtorReadChannel(socket.openReadChannel())
override fun openWriteChannel(): WriteChannel = KtorWriteChannel(socket.openWriteChannel())
override fun close() {
socket.close()
sm.close()
}
}
public class KtorReadChannel(private val readChannel: ByteReadChannel) : ReadChannel {
override suspend fun readByte(): Byte = readChannel.readByte()
override suspend fun readBytes(length: Int): ByteArray = readChannel.readByteArray(length)
override suspend fun readFully(out: ByteArray, start: Int, end: Int): Unit =
readChannel.readFully(out, start, end)
}
public class KtorWriteChannel(private val writeChannel: ByteWriteChannel) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
writeChannel.writeFully(value, startIndex, endIndex)
}
override suspend fun flush(): Unit = writeChannel.flush()
}
@@ -16,9 +16,10 @@ import dev.whyoleg.cryptography.algorithms.SHA1
internal val provider = CryptographyProvider.Default
public suspend fun rsaEncrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
public fun rsaEncrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
val provider = CryptographyProvider.Default
val rsa = provider.get(RSA.PKCS1)
val publicKey = rsa.publicKeyDecoder(SHA1).decodeFromByteArray(RSA.PublicKey.Format.DER, publicKeyBytes)
return publicKey.encryptor().encrypt(data)
val publicKey = rsa.publicKeyDecoder(SHA1)
.decodeFromByteArrayBlocking(RSA.PublicKey.Format.DER, publicKeyBytes)
return publicKey.encryptor().encryptBlocking(data)
}
@@ -12,11 +12,11 @@ package cn.rtast.libmc.protocol.crypto
import dev.whyoleg.cryptography.DelicateCryptographyApi
import dev.whyoleg.cryptography.algorithms.SHA1
public suspend fun minecraftServerIdHash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String {
public fun minecraftServerIdHash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String {
val serverIdBytes = serverId.encodeToByteArray()
for (b in serverIdBytes) require((b.toInt() and 0xFF) <= 0x7F) { "serverId contains non-US-ASCII character" }
val data = serverIdBytes + secretKey + publicKey
val hash = provider.get(SHA1).hasher().hash(data)
val hash = provider.get(SHA1).hasher().hashBlocking(data)
return mcDigestToString(hash)
}
@@ -29,7 +29,7 @@ class TestJvmClient {
Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
// null,
accessToken,
crypto = DefaultProtocolContext
contextBuilder = DefaultProtocolContext
)
// cli.on<ClientboundSystemChatMessagePacket> { println(it) }
// cli.on<ClientboundLoginSuccessPacket> { println(it) }
+1 -1
View File
@@ -22,7 +22,7 @@ kotlin {
commonTest.dependencies {
implementation(kotlin("test"))
implementation(project(":protocol-encrypt"))
implementation(project(":protocol-context"))
implementation(libs.kotlinx.coroutines.test)
}
@@ -6,7 +6,6 @@
package cn.rtast.libmc.protocol.client
import cn.rtast.libmc.LibMCContext
import cn.rtast.libmc.crypto.ProtocolContext
import cn.rtast.libmc.crypto.ProtocolContextBuilder
import cn.rtast.libmc.protocol.event.InternalPacketDispatcher
@@ -24,22 +23,23 @@ import kotlin.uuid.Uuid
public class MinecraftClient internal constructor(
private val host: String,
private val port: Int = 25565,
private val port: Int,
private val username: String,
internal val uuid: Uuid,
internal val accessToken: String?,
context: LibMCContext,
parentJob: Job?,
private val ioDispatcher: CoroutineDispatcher,
cryptoContext: ProtocolContext,
protocolContext: ProtocolContext,
) : PacketEventDispatcher(), CoroutineScope {
internal val rsa1024Encryptor = cryptoContext.rsaEncryptor
internal val serverIdHasher = cryptoContext.sha1Hasher
internal val authProvider = cryptoContext.authProvider
internal val rsa1024Encryptor = protocolContext.rsaEncryptor
internal val serverIdHasher = protocolContext.sha1Hasher
internal val authProvider = protocolContext.authProvider
internal val stateMachine = ClientStateMachine()
public val networkChannel: NetworkChannel = NetworkChannel(
host, port, context, stateMachine, cryptoContext.cipherFactory, this
host, port, stateMachine,
protocolContext.cipherFactory,
this, protocolContext
)
private val internalPacketDispatcher = InternalPacketDispatcher(this, authProvider)
@@ -95,21 +95,19 @@ public fun createMinecraftClient(
username: String,
uuid: Uuid = generateOfflineUuid(username),
accessToken: String?,
context: LibMCContext = LibMCContext(),
parentJob: Job? = null,
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
crypto: ProtocolContextBuilder.() -> Unit,
contextBuilder: ProtocolContextBuilder.() -> Unit,
): MinecraftClient {
val cryptoContext = ProtocolContextBuilder(accessToken != null).apply(crypto).build()
val context = ProtocolContextBuilder(accessToken != null).apply(contextBuilder).build()
return MinecraftClient(
host = host,
port = port,
username = username,
uuid = uuid,
accessToken = accessToken,
context = context,
parentJob = parentJob,
ioDispatcher = ioDispatcher,
cryptoContext = cryptoContext
protocolContext = context
)
}
@@ -8,8 +8,8 @@
package cn.rtast.libmc.protocol.network
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.stream.ReadChannel
import cn.rtast.libmc.stream.WriteChannel
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.WriteChannel
/**
* AES-128-CFB8 ***ciphered*** read channel
@@ -17,7 +17,7 @@ import cn.rtast.libmc.stream.WriteChannel
internal class CipherReadChannel(
private val delegate: ReadChannel,
private val crypto: NetworkCipher,
) : ReadChannel() {
) : ReadChannel {
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
delegate.readFully(out, start, end)
val length = end - start
@@ -43,7 +43,7 @@ internal class CipherReadChannel(
internal class CipherWriteChannel(
private val delegate: WriteChannel,
private val crypto: NetworkCipher,
) : WriteChannel() {
) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
val length = endIndex - startIndex
if (length <= 0) return
@@ -52,7 +52,5 @@ internal class CipherWriteChannel(
delegate.writeFully(encrypted, 0, length)
}
override suspend fun flush() {
delegate.flush()
}
override suspend fun flush() = delegate.flush()
}
@@ -6,17 +6,18 @@
package cn.rtast.libmc.protocol.network
import cn.rtast.libmc.LibMCContext
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.crypto.ProtocolContext
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.network.wrap
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.writeBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.client.ClientStateMachine
import cn.rtast.libmc.protocol.event.PacketEventDispatcher
import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.stream.wrap
import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec.clientboundGameProtocols
import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec.serverboundGameProtocols
import cn.rtast.libmc.zlibCompress
import cn.rtast.libmc.zlibDecompress
import kotlin.concurrent.Volatile
@@ -24,17 +25,17 @@ import kotlin.concurrent.Volatile
public class NetworkChannel internal constructor(
host: String,
port: Int,
context: LibMCContext,
private val stateMachine: ClientStateMachine,
cipherProvider: (ByteArray) -> NetworkCipher,
private val dispatcher: PacketEventDispatcher,
protocolContext: ProtocolContext,
) {
internal val session: NetworkSession = NetworkSession(host, port, context, cipherProvider)
internal val session: NetworkSession = NetworkSession(host, port, cipherProvider, protocolContext)
@Volatile
private var threshold = -1
public fun connect(): Unit = session.connect()
public suspend fun connect(): Unit = session.connect()
public fun setCompression(threshold: Int): Unit = run { this.threshold = threshold }
/**
@@ -44,18 +45,17 @@ public class NetworkChannel internal constructor(
*/
public suspend fun readNextPacket(): MinecraftPacket {
val packetLength = session.readVarInt()
val rawFrameBytes = session.readBytes(packetLength)
val frameBuf = rawFrameBytes.wrap()
val frameBuf = session.readBytes(packetLength).wrap()
val payloadBuf = if (threshold < 0) frameBuf else {
val dataLength = frameBuf.readVarInt()
val remainingBytes = frameBuf.readBytes(frameBuf.remaining.toInt())
if (dataLength == 0) remainingBytes.wrap() else remainingBytes.zlibDecompress(dataLength).wrap()
if (dataLength == 0) frameBuf else {
val compressedBytes = frameBuf.toByteArray()
compressedBytes.zlibDecompress(dataLength).wrap()
}
}
val currentState = stateMachine.currentState
val packetId = payloadBuf.readVarInt()
val packet = GamePacketsProtocolCodec.clientboundGameProtocols
.getRegistry(currentState)
.decodePacket(packetId, payloadBuf)
val packet = clientboundGameProtocols.getRegistry(currentState).decodePacket(packetId, payloadBuf)
dispatcher.dispatchReceive(packet)
return packet
}
@@ -67,9 +67,7 @@ public class NetworkChannel internal constructor(
*/
public suspend fun sendPacket(packet: MinecraftPacket) {
val uncompressedBodyBuf = BytesBuffer()
GamePacketsProtocolCodec.serverboundGameProtocols
.getRegistry(stateMachine.currentState)
.encodePacket(uncompressedBodyBuf, packet)
serverboundGameProtocols.getRegistry(stateMachine.currentState).encodePacket(uncompressedBodyBuf, packet)
val uncompressedData = uncompressedBodyBuf.toByteArray()
val frameBuffer = BytesBuffer()
if (threshold < 0) {
@@ -6,19 +6,20 @@
package cn.rtast.libmc.protocol.network
import cn.rtast.libmc.LibMCContext
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.stream.ReadChannel
import cn.rtast.libmc.stream.Socket
import cn.rtast.libmc.stream.WriteChannel
import cn.rtast.libmc.crypto.ProtocolContext
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.WriteChannel
import cn.rtast.libmc.primitives.readVarInt
public class NetworkSession internal constructor(
private val host: String,
private val port: Int,
private val context: LibMCContext,
private var cipherProvider: (ByteArray) -> NetworkCipher,
private val context: ProtocolContext,
) {
private var socket: Socket? = null
private var socket: RawSocket? = null
public var readChannel: ReadChannel? = null
private set
@@ -26,47 +27,28 @@ public class NetworkSession internal constructor(
public var writeChannel: WriteChannel? = null
private set
public fun connect() {
val sk = Socket(host, port, context)
public suspend fun connect() {
val sk = context.createSocket(host, port)
sk.connect()
this.socket = sk
this.readChannel = sk.openReadChannel()
this.writeChannel = sk.openWriteChannel()
}
public fun enableEncryption(sharedKey: ByteArray) {
val currentRead = requireNotNull(readChannel) { "ReadChannel not connected" }
val currentWrite = requireNotNull(writeChannel) { "WriteChannel not connected" }
val currentRead = requireNotNull(readChannel)
val currentWrite = requireNotNull(writeChannel)
val cipher = cipherProvider(sharedKey)
this.readChannel = CipherReadChannel(currentRead, cipher)
this.writeChannel = CipherWriteChannel(currentWrite, cipher)
}
internal suspend fun readByte(): Byte {
val channel = requireNotNull(readChannel) { "ReadChannel not connected" }
return channel.readByte()
}
internal suspend fun readBytes(length: Int): ByteArray = requireNotNull(readChannel).readBytes(length)
internal suspend fun readBytes(length: Int): ByteArray {
val channel = requireNotNull(readChannel) { "ReadChannel not connected" }
return channel.readBytes(length)
}
internal suspend fun readVarInt(): Int {
var numRead = 0
var result = 0
var read: Byte
do {
read = readByte()
val value = (read.toInt() and 0x7F)
result = result or (value shl (7 * numRead))
numRead++
if (numRead > 5) throw IllegalArgumentException("VarInt is too big")
} while ((read.toInt() and 0x80) != 0)
return result
}
internal suspend fun readVarInt(): Int = requireNotNull(readChannel).readVarInt()
internal suspend fun writeFully(data: ByteArray) {
val channel = requireNotNull(writeChannel) { "WriteChannel not connected" }
val channel = requireNotNull(writeChannel)
channel.writeFully(data, 0, data.size)
channel.flush()
}
@@ -10,17 +10,17 @@ package cn.rtast.libmc.protocol.packet.configuration
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.writeMcString
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public data class KnownPacks(val namespace: String, val id: String, val version: String) {
internal companion object Codec : PacketCodec<KnownPacks> {
override suspend fun encode(buffer: BytesBuffer, value: KnownPacks) {
override fun encode(buffer: BytesBuffer, value: KnownPacks) {
buffer.writeMcString(value.namespace)
buffer.writeMcString(value.id)
buffer.writeMcString(value.version)
}
override suspend fun decode(buffer: BytesBuffer): KnownPacks {
override fun decode(buffer: BytesBuffer): KnownPacks {
val namespace = buffer.readMcString()
val id = buffer.readMcString()
val version = buffer.readMcString()
@@ -14,7 +14,7 @@ import cn.rtast.libmc.primitives.readUuid
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import kotlin.uuid.Uuid
public data class ClientboundAddResourcePackPacket(
@@ -25,8 +25,8 @@ public data class ClientboundAddResourcePackPacket(
val prompt: TextComponent,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundAddResourcePackPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundAddResourcePackPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundAddResourcePackPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundAddResourcePackPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundAddResourcePackPacket {
val uuid = buffer.readUuid()
val url = buffer.readMcString()
val hash = buffer.readMcString()
@@ -7,12 +7,12 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
public data object ClientboundClearDialogPacket : MinecraftPacket,
PacketCodec<ClientboundClearDialogPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundClearDialogPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundClearDialogPacket = ClientboundClearDialogPacket
override fun encode(buffer: BytesBuffer, value: ClientboundClearDialogPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundClearDialogPacket = ClientboundClearDialogPacket
}
@@ -7,15 +7,15 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString
public data class ClientboundCodeOfConductPacket(val codeOfConduct: String) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundCodeOfConductPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundCodeOfConductPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundCodeOfConductPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundCodeOfConductPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundCodeOfConductPacket {
return ClientboundCodeOfConductPacket(buffer.readMcString())
}
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound
@@ -15,8 +15,8 @@ import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ClientboundConfigurationShowDialogPacket(val dialog: NBTCompound) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundConfigurationShowDialogPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundConfigurationShowDialogPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundConfigurationShowDialogPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundConfigurationShowDialogPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundConfigurationShowDialogPacket {
return ClientboundConfigurationShowDialogPacket(buffer.readNetworkNBTCompound())
}
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.protocol.protocol.game.Identifier
@@ -15,8 +15,8 @@ import cn.rtast.libmc.protocol.protocol.game.readIdentifier
public data class ClientboundCookieRequestPacket(val key: Identifier) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundCookieRequestPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundCookieRequestPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundCookieRequestPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundCookieRequestPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundCookieRequestPacket {
return ClientboundCookieRequestPacket(key = buffer.readIdentifier())
}
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.protocol.protocol.game.Identifier
@@ -18,10 +18,10 @@ public data class ClientboundCustomPayloadPacket(
val data: ByteArray,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundCustomPayloadPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundCustomPayloadPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundCustomPayloadPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundCustomPayloadPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundCustomPayloadPacket {
val channel = buffer.readIdentifier()
val data = buffer.readRemainingBytes()
val data = buffer.toByteArray()
return ClientboundCustomPayloadPacket(channel, data)
}
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString
@@ -17,12 +17,12 @@ import cn.rtast.libmc.primitives.writeMcString
public data class ClientboundCustomReportDetailsPacket(val details: List<ReportDetail>) : MinecraftPacket {
public data class ReportDetail(val title: String, val description: String) {
internal companion object Codec : PacketCodec<ReportDetail> {
override suspend fun encode(buffer: BytesBuffer, value: ReportDetail) {
override fun encode(buffer: BytesBuffer, value: ReportDetail) {
buffer.writeMcString(value.title)
buffer.writeMcString(value.description)
}
override suspend fun decode(buffer: BytesBuffer): ReportDetail {
override fun decode(buffer: BytesBuffer): ReportDetail {
val title = buffer.readMcString()
val description = buffer.readMcString()
return ReportDetail(title, description)
@@ -31,8 +31,8 @@ public data class ClientboundCustomReportDetailsPacket(val details: List<ReportD
}
internal companion object Codec : PacketCodec<ClientboundCustomReportDetailsPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundCustomReportDetailsPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundCustomReportDetailsPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundCustomReportDetailsPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundCustomReportDetailsPacket {
val detailCount = buffer.readVarInt()
val details = ArrayList<ReportDetail>(detailCount)
repeat(detailCount) { details.add(ReportDetail.decode(buffer)) }
@@ -11,12 +11,12 @@ import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
public data class ClientboundDisconnectConfigurationPacket(val reason: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket {
return ClientboundDisconnectConfigurationPacket(reason = buffer.readTextComponent())
}
}
@@ -7,14 +7,14 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
public data object ClientboundFinishConfigurationPacket : MinecraftPacket,
PacketCodec<ClientboundFinishConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundFinishConfigurationPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundFinishConfigurationPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundFinishConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundFinishConfigurationPacket {
return ClientboundFinishConfigurationPacket
}
}
@@ -7,14 +7,14 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
public data class ClientboundKeepAliveConfigurationPacket(val id: Long) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundKeepAliveConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundKeepAliveConfigurationPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundKeepAliveConfigurationPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundKeepAliveConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundKeepAliveConfigurationPacket {
return ClientboundKeepAliveConfigurationPacket(buffer.readLong())
}
}
@@ -7,14 +7,14 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
public data class ClientboundPingConfigurationPacket(val id: Int) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundPingConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundPingConfigurationPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundPingConfigurationPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundPingConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundPingConfigurationPacket {
return ClientboundPingConfigurationPacket(id = buffer.readInt())
}
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
@@ -23,8 +23,8 @@ public data class ClientboundRegistryDataPacket(
val entries: List<RegistryEntry>,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundRegistryDataPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundRegistryDataPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundRegistryDataPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundRegistryDataPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundRegistryDataPacket {
val id = buffer.readIdentifier()
val entryCount = buffer.readVarInt()
val entries = ArrayList<RegistryEntry>(entryCount)
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readUuid
@@ -15,8 +15,8 @@ import kotlin.uuid.Uuid
public data class ClientboundRemoveResourcePackPacket(val uuid: Uuid) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundRemoveResourcePackPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundRemoveResourcePackPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundRemoveResourcePackPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundRemoveResourcePackPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundRemoveResourcePackPacket {
val uuid = buffer.readUuid()
return ClientboundRemoveResourcePackPacket(uuid)
}
@@ -7,12 +7,12 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
public data object ClientboundResetChatPacket : MinecraftPacket,
PacketCodec<ClientboundResetChatPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundResetChatPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundResetChatPacket = ClientboundResetChatPacket
override fun encode(buffer: BytesBuffer, value: ClientboundResetChatPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundResetChatPacket = ClientboundResetChatPacket
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
@@ -15,8 +15,8 @@ import cn.rtast.libmc.protocol.packet.configuration.KnownPacks
public data class ClientboundSelectKnownPacksPacket(val knownPacks: List<KnownPacks>) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSelectKnownPacksPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundSelectKnownPacksPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundSelectKnownPacksPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundSelectKnownPacksPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSelectKnownPacksPacket {
val packsCount = buffer.readVarInt()
val packs = List(packsCount) { KnownPacks.decode(buffer) }
return ClientboundSelectKnownPacksPacket(packs)
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
@@ -15,8 +15,8 @@ import cn.rtast.libmc.protocol.protocol.game.registry.report.ServerLink
public data class ClientboundServerLinksPacket(val links: List<ServerLink>) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundServerLinksPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundServerLinksPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundServerLinksPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundServerLinksPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundServerLinksPacket {
val count = buffer.readVarInt()
val links = ArrayList<ServerLink>(count)
repeat(count) { links.add(ServerLink.decode(buffer)) }
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readPrefixedByteArray
@@ -22,8 +22,8 @@ public data class ClientboundStoreCookiePacket(
val payload: ByteArray,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundStoreCookiePacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundStoreCookiePacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundStoreCookiePacket {
override fun encode(buffer: BytesBuffer, value: ClientboundStoreCookiePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundStoreCookiePacket {
val key = buffer.readIdentifier()
val payload = buffer.readPrefixedByteArray()
return ClientboundStoreCookiePacket(key, payload)
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString
@@ -15,8 +15,8 @@ import cn.rtast.libmc.primitives.readVarInt
public data class ClientboundTransferPacket(val host: String, val port: Int) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundTransferPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundTransferPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundTransferPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundTransferPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundTransferPacket {
val host = buffer.readMcString()
val port = buffer.readVarInt()
return ClientboundTransferPacket(host, port)
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
@@ -16,8 +16,8 @@ import cn.rtast.libmc.protocol.protocol.game.readIdentifier
public data class ClientboundUpdateEnabledFeaturesPacket(val features: List<Identifier>) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundUpdateEnabledFeaturesPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundUpdateEnabledFeaturesPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundUpdateEnabledFeaturesPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundUpdateEnabledFeaturesPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundUpdateEnabledFeaturesPacket {
val featureCount = buffer.readVarInt()
val features = ArrayList<Identifier>(featureCount)
repeat(featureCount) { features.add(buffer.readIdentifier()) }
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
@@ -19,13 +19,13 @@ import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class ClientboundUpdateTagsPacket(val registries: List<TaggedRegistry>) : MinecraftPacket {
public data class TaggedRegistry(val registryId: Identifier, val tags: List<Tag>) {
internal companion object Codec : PacketCodec<TaggedRegistry> {
override suspend fun encode(buffer: BytesBuffer, value: TaggedRegistry) {
override fun encode(buffer: BytesBuffer, value: TaggedRegistry) {
buffer.writeIdentifier(value.registryId)
buffer.writeVarInt(value.tags.size)
value.tags.forEach { Tag.encode(buffer, it) }
}
override suspend fun decode(buffer: BytesBuffer): TaggedRegistry {
override fun decode(buffer: BytesBuffer): TaggedRegistry {
val id = buffer.readIdentifier()
val tagCount = buffer.readVarInt()
val tags = ArrayList<Tag>(tagCount)
@@ -37,13 +37,13 @@ public data class ClientboundUpdateTagsPacket(val registries: List<TaggedRegistr
public data class Tag(val name: Identifier, val entries: IntArray) {
internal companion object Codec : PacketCodec<Tag> {
override suspend fun encode(buffer: BytesBuffer, value: Tag) {
override fun encode(buffer: BytesBuffer, value: Tag) {
buffer.writeIdentifier(value.name)
buffer.writeVarInt(value.entries.size)
value.entries.forEach { buffer.writeVarInt(it) }
}
override suspend fun decode(buffer: BytesBuffer): Tag {
override fun decode(buffer: BytesBuffer): Tag {
val name = buffer.readIdentifier()
val entrySize = buffer.readVarInt()
val entries = ArrayList<Int>(entrySize)
@@ -69,8 +69,8 @@ public data class ClientboundUpdateTagsPacket(val registries: List<TaggedRegistr
}
internal companion object Codec : PacketCodec<ClientboundUpdateTagsPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundUpdateTagsPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundUpdateTagsPacket {
override fun encode(buffer: BytesBuffer, value: ClientboundUpdateTagsPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundUpdateTagsPacket {
val registryCount = buffer.readVarInt()
val registries = ArrayList<TaggedRegistry>(registryCount)
repeat(registryCount) { registries.add(TaggedRegistry.decode(buffer)) }
@@ -7,13 +7,13 @@
package cn.rtast.libmc.protocol.packet.configuration.serverbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket
public data object ServerboundAcceptCodeOfConductPacket : MinecraftPacket,
PacketCodec<ServerboundAcceptCodeOfConductPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ServerboundAcceptCodeOfConductPacket) {}
override suspend fun decode(buffer: BytesBuffer): ServerboundAcceptCodeOfConductPacket =
override fun encode(buffer: BytesBuffer, value: ServerboundAcceptCodeOfConductPacket) {}
override fun decode(buffer: BytesBuffer): ServerboundAcceptCodeOfConductPacket =
ServerboundAcceptCodeOfConductPacket
}
@@ -7,13 +7,13 @@
package cn.rtast.libmc.protocol.packet.configuration.serverbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket
public data object ServerboundAckFinishConfigurationPacket : MinecraftPacket,
PacketCodec<ServerboundAckFinishConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ServerboundAckFinishConfigurationPacket) {}
override suspend fun decode(buffer: BytesBuffer): ServerboundAckFinishConfigurationPacket =
override fun encode(buffer: BytesBuffer, value: ServerboundAckFinishConfigurationPacket) {}
override fun decode(buffer: BytesBuffer): ServerboundAckFinishConfigurationPacket =
throw UnsupportedOperationException()
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.packet.configuration.serverbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.primitives.writeVarInt
@@ -16,7 +16,7 @@ import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class ServerboundCookieResponsePacket(val key: Identifier, val payload: ByteArray?) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundCookieResponsePacket> {
override suspend fun encode(buffer: BytesBuffer, value: ServerboundCookieResponsePacket) {
override fun encode(buffer: BytesBuffer, value: ServerboundCookieResponsePacket) {
buffer.writeIdentifier(value.key)
if (value.payload != null) {
buffer.writeBoolean(true)
@@ -25,7 +25,7 @@ public data class ServerboundCookieResponsePacket(val key: Identifier, val paylo
} else buffer.writeBoolean(false)
}
override suspend fun decode(buffer: BytesBuffer): ServerboundCookieResponsePacket =
override fun decode(buffer: BytesBuffer): ServerboundCookieResponsePacket =
throw UnsupportedOperationException()
}
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.configuration.serverbound
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket
public data class ServerboundKeepAliveConfigurationPacket(val id: Long) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundKeepAliveConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ServerboundKeepAliveConfigurationPacket) {
override fun encode(buffer: BytesBuffer, value: ServerboundKeepAliveConfigurationPacket) {
buffer.writeLong(value.id)
}
override suspend fun decode(buffer: BytesBuffer): ServerboundKeepAliveConfigurationPacket =
override fun decode(buffer: BytesBuffer): ServerboundKeepAliveConfigurationPacket =
throw UnsupportedOperationException()
}
}
Loaded 100 of 318 files, more files were not shown because too many files have changed in this diff. Show more