Split rconlib and mcping

This commit is contained in:
2026-09-04 00:19:17 +08:00
parent 23e7a827db
commit 350b900aaa
38 files changed
+350 -268

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
explicitApi()
linuxX64()
linuxArm64()
macosArm64()
mingwX64()
iosArm64()
iosSimulatorArm64()
jvm { compilerOptions.jvmTarget = JvmTarget.JVM_1_8 }
sourceSets {
jvmMain.dependencies {
// no dependencies needed
}
nativeMain.dependencies {
implementation("io.ktor:ktor-network:3.5.2")
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.9.1")
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
}
}
compilerOptions.freeCompilerArgs.addAll("-Xexpect-actual-classes")
}
@@ -0,0 +1,28 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
@Suppress("CLASSNAME")
public expect class _Buffer {
public constructor()
public constructor(bytes: ByteArray)
public fun writeByte(value: Byte)
public fun writeShort(value: Short)
public fun writeLong(value: Long)
public fun writeBytes(bytes: ByteArray)
public fun readByte(): Byte
public fun readShort(): Short
public fun readLong(): Long
public fun readBytes(length: Int): ByteArray
public fun toByteArray(): ByteArray
public val size: Int
}
public fun ByteArray.wrap(): _Buffer = _Buffer(this)
@@ -0,0 +1,22 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
@Suppress("CLASSNAME")
public expect class _ReadChannel {
public fun readByte(): Byte
public fun readBytes(length: Int): ByteArray
public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
public fun readLong(): Long
}
@Suppress("CLASSNAME")
public expect class _WriteChannel {
public fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
public fun flush()
}
@@ -0,0 +1,14 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
/**
* An object class used to pass some parameters
* that exists only on native targets
*/
public expect class PingContext()
@@ -0,0 +1,21 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
@Suppress("CLASSNAME")
public expect class _Socket public constructor(host: String, port: Int, context: PingContext) {
public fun openReadChannel(): _ReadChannel
public fun openWriteChannel(): _WriteChannel
public fun close()
}
@Suppress("CLASSNAME")
public expect class _UdpSocket public constructor(host: String, port: Int, context: PingContext) {
public fun sendAndReceive(data: ByteArray): ByteArray
public fun close()
}
@@ -0,0 +1,92 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
import java.io.ByteArrayOutputStream
@Suppress("CLASSNAME")
public actual class _Buffer {
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 fun writeByte(value: Byte) {
outStream.write(value.toInt())
}
public actual fun writeShort(value: Short) {
val v = value.toInt()
outStream.write(v shr 8)
outStream.write(v)
}
public actual fun writeLong(value: Long) {
outStream.write((value shr 56).toInt())
outStream.write((value shr 48).toInt())
outStream.write((value shr 40).toInt())
outStream.write((value shr 32).toInt())
outStream.write((value shr 24).toInt())
outStream.write((value shr 16).toInt())
outStream.write((value shr 8).toInt())
outStream.write(value.toInt())
}
public actual fun writeBytes(bytes: ByteArray) {
outStream.write(bytes)
}
private fun ensureReadArray(): ByteArray {
var buf = readBuffer
if (buf == null) {
buf = outStream.toByteArray()
readBuffer = buf
}
return buf
}
public actual fun readByte(): Byte {
val array = ensureReadArray()
if (readOffset >= array.size) throw IndexOutOfBoundsException("Buffer underflow")
return array[readOffset++]
}
public actual fun readShort(): Short {
val b1 = readByte().toInt() and 0xFF
val b2 = readByte().toInt() and 0xFF
return ((b1 shl 8) or b2).toShort()
}
public actual fun readLong(): Long {
return (readByte().toLong() and 0xFF shl 56) or
(readByte().toLong() and 0xFF shl 48) or
(readByte().toLong() and 0xFF shl 40) or
(readByte().toLong() and 0xFF shl 32) or
(readByte().toLong() and 0xFF shl 24) or
(readByte().toLong() and 0xFF shl 16) or
(readByte().toLong() and 0xFF shl 8) or
(readByte().toLong() and 0xFF)
}
public actual 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 fun toByteArray(): ByteArray = outStream.toByteArray()
public actual val size: Int
get() = outStream.size()
}
@@ -0,0 +1,76 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
import java.io.EOFException
import java.io.InputStream
import java.io.OutputStream
@Suppress("CLASSNAME")
public actual class _ReadChannel {
private val _inputStream: InputStream
public constructor(inputStream: InputStream) {
_inputStream = inputStream
}
public actual fun readByte(): Byte = _inputStream.read().toByte()
public actual fun readBytes(length: Int): ByteArray = _inputStream.readNBytes(length)
public actual 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 IllegalStateException("End of stream")
bytesRead += read
}
}
public actual fun readLong(): Long {
val bytes = ByteArray(8)
var read = 0
while (read < 8) {
val count = _inputStream.read(bytes, read, 8 - read)
if (count == -1) throw EOFException()
read += count
}
return ((bytes[0].toLong() and 0xFF shl 56) or
(bytes[1].toLong() and 0xFF shl 48) or
(bytes[2].toLong() and 0xFF shl 40) or
(bytes[3].toLong() and 0xFF shl 32) or
(bytes[4].toLong() and 0xFF shl 24) or
(bytes[5].toLong() and 0xFF shl 16) or
(bytes[6].toLong() and 0xFF shl 8) or
(bytes[7].toLong() and 0xFF))
}
}
@Suppress("CLASSNAME")
public actual class _WriteChannel {
private val _outputStream: OutputStream
public constructor(outputStream: OutputStream) {
_outputStream = outputStream
}
public actual fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int): Unit =
_outputStream.write(value, startIndex, endIndex - startIndex)
public actual fun flush(): Unit = _outputStream.flush()
}
private fun InputStream.readNBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
var totalRead = 0
while (totalRead < length) {
val read = this.read(buffer, totalRead, length - totalRead)
if (read == -1) throw EOFException()
totalRead += read
}
return buffer
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
/**
* No Context for jvm targets
*/
public actual class PingContext
@@ -0,0 +1,40 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.net.Socket as JvmSocket
@Suppress("CLASSNAME")
public actual class _Socket public actual constructor(host: String, port: Int, context: PingContext) {
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()
}
@Suppress("CLASSNAME")
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: PingContext) {
private val socket = DatagramSocket().apply {
soTimeout = 3000
connect(InetSocketAddress(host, port))
}
public actual fun sendAndReceive(data: ByteArray): ByteArray {
socket.send(DatagramPacket(data, data.size))
val buf = ByteArray(2048)
val receivePacket = DatagramPacket(buf, buf.size)
socket.receive(receivePacket)
return buf.copyOf(receivePacket.length)
}
public actual fun close(): Unit = socket.close()
}
@@ -0,0 +1,38 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
import kotlinx.io.Buffer
import kotlinx.io.readByteArray
@Suppress("CLASSNAME")
public actual class _Buffer {
private val _delegateBuf: Buffer
public actual constructor() {
_delegateBuf = Buffer()
}
public actual constructor(bytes: ByteArray) {
_delegateBuf = Buffer().apply { write(bytes) }
}
public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value)
public actual fun writeShort(value: Short): Unit = _delegateBuf.writeShort(value)
public actual fun writeLong(value: Long): Unit = _delegateBuf.writeLong(value)
public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes)
public actual fun readByte(): Byte = _delegateBuf.readByte()
public actual fun readShort(): Short = _delegateBuf.readShort()
public actual fun readLong(): Long = _delegateBuf.readLong()
public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
public actual fun toByteArray(): ByteArray = _delegateBuf.peek().readByteArray()
public actual val size: Int
get() = _delegateBuf.size.toInt()
}
@@ -0,0 +1,40 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
import io.ktor.utils.io.*
import kotlinx.coroutines.runBlocking
@Suppress("CLASSNAME")
public actual class _ReadChannel {
private val _readChannel: ByteReadChannel
public constructor(readChannel: ByteReadChannel) {
_readChannel = readChannel
}
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
public actual fun readBytes(length: Int): ByteArray = runBlocking { _readChannel.readByteArray(length) }
public actual fun readFully(out: ByteArray, start: Int, end: Int): Unit =
runBlocking { _readChannel.readFully(out, start, end) }
public actual fun readLong(): Long = runBlocking { _readChannel.readLong() }
}
@Suppress("CLASSNAME")
public actual class _WriteChannel {
private val _writeChannel: ByteWriteChannel
public constructor(writeChannel: ByteWriteChannel) {
_writeChannel = writeChannel
}
public actual fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int): Unit =
runBlocking { _writeChannel.writeFully(value, startIndex, endIndex) }
public actual fun flush(): Unit = runBlocking { _writeChannel.flush() }
}
@@ -0,0 +1,23 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
@file:Suppress("PropertyName")
package cn.rtast.mcping.platform
import io.ktor.network.selector.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
public actual class PingContext 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
}
}
@@ -0,0 +1,47 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
import io.ktor.network.sockets.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.runBlocking
import kotlinx.io.readByteArray
@Suppress("CLASSNAME")
public actual class _Socket public actual constructor(host: String, port: Int, context: PingContext) {
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()
}
}
@Suppress("CLASSNAME")
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: PingContext) {
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 fun sendAndReceive(data: ByteArray): ByteArray = runBlocking {
val packet = buildPacket { writeFully(data) }
socket.send(Datagram(packet, remoteAddress))
socket.receive().packet.readByteArray()
}
public actual fun close() {
socket.close()
if (ctx._autoCloseSelectorManager) ctx._selectorManager.close()
}
}