rcon client supports
This commit is contained in:
27 files changed
+412
-93
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
# mcping
|
# mcping
|
||||||
|
|
||||||
A lightweight motd ping library for kotlin multiplatform and java,
|
A lightweight motd ping / rcon client library for kotlin multiplatform and java,
|
||||||
no dependencies in `jvm` target except `kotlin-stdlib`
|
no dependencies in `jvm` target except `kotlin-stdlib`
|
||||||
|
|
||||||
native platforms required `ktor-network` and `kotlinx-io`
|
native platforms required `ktor-network` and `kotlinx-io`
|
||||||
@@ -23,12 +23,15 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("cn.rtast.mcping:mcping:0.0.2")
|
implementation("cn.rtast.mcping:mcping:0.0.3")
|
||||||
|
implementation("cn.rtast.mcping:rconlib:0.0.3")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Get latest version at https://repo.rtast.cn/packages/-/cn.rtast.mcping:mcping/
|
> Get latest version at https://repo.rtast.cn/packages/-/cn.rtast.mcping:mcping/
|
||||||
|
|
||||||
|
# MC Ping
|
||||||
|
|
||||||
## Kotlin example
|
## Kotlin example
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
@@ -69,6 +72,36 @@ void main() {
|
|||||||
> An example of java ping json response can be found at [ping-response-example](example/java-ping-response.json) (Formatted),
|
> An example of java ping json response can be found at [ping-response-example](example/java-ping-response.json) (Formatted),
|
||||||
> a raw response of bedrock ping response can be found at [bedrock-pinng-raw-response](example/bedrock-pinng-raw-response.txt)
|
> a raw response of bedrock ping response can be found at [bedrock-pinng-raw-response](example/bedrock-pinng-raw-response.txt)
|
||||||
|
|
||||||
|
|
||||||
|
# rcon client
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun main() {
|
||||||
|
val host = "127.0.0.1"
|
||||||
|
val port = 25575
|
||||||
|
val client = rconClient(host, port)
|
||||||
|
val authed = client.connect("123456")
|
||||||
|
if (authed) println(client.command("list")) else throw IllegalArgumentException("incorrect password")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> also supports java
|
||||||
|
|
||||||
|
```java
|
||||||
|
void main() {
|
||||||
|
String host = "127.0.0.1";
|
||||||
|
int port = 25575;
|
||||||
|
String password = "123456";
|
||||||
|
RCONClient rconClient = Rconlib.rconClient(host, port);
|
||||||
|
boolean authed = rconClient.connect(password);
|
||||||
|
if (authed) {
|
||||||
|
System.out.println(rconClient.command("list"));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("incorrect password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Open Source
|
# Open Source
|
||||||
|
|
||||||
Open source under [Apache-2.0](LICENSE)
|
Open source under [Apache-2.0](LICENSE)
|
||||||
@@ -13,15 +13,18 @@ public expect class _Buffer {
|
|||||||
public constructor(bytes: ByteArray)
|
public constructor(bytes: ByteArray)
|
||||||
|
|
||||||
public fun writeByte(value: Byte)
|
public fun writeByte(value: Byte)
|
||||||
public fun writeShort(value: Short)
|
public fun writeShort(value: Short, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
|
||||||
public fun writeLong(value: Long)
|
public fun writeInt(value: Int, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
|
||||||
|
public fun writeLong(value: Long, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
|
||||||
public fun writeBytes(bytes: ByteArray)
|
public fun writeBytes(bytes: ByteArray)
|
||||||
|
|
||||||
public fun readByte(): Byte
|
public fun readByte(): Byte
|
||||||
public fun readShort(): Short
|
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
|
||||||
public fun readLong(): Long
|
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
|
||||||
|
public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
|
||||||
public fun readBytes(length: Int): ByteArray
|
public fun readBytes(length: Int): ByteArray
|
||||||
public fun toByteArray(): ByteArray
|
public fun toByteArray(): ByteArray
|
||||||
|
public fun close()
|
||||||
public val size: Int
|
public val size: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.mcping.platform
|
||||||
|
|
||||||
|
public enum class ByteOrder {
|
||||||
|
BIG_ENDIAN, LITTLE_ENDIAN
|
||||||
|
}
|
||||||
@@ -10,9 +10,11 @@ package cn.rtast.mcping.platform
|
|||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _ReadChannel {
|
public expect class _ReadChannel {
|
||||||
public fun readByte(): Byte
|
public fun readByte(): Byte
|
||||||
|
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
|
||||||
|
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
|
||||||
|
public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
|
||||||
public fun readBytes(length: Int): ByteArray
|
public fun readBytes(length: Int): ByteArray
|
||||||
public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
|
public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
|
||||||
public fun readLong(): Long
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ package cn.rtast.mcping.platform
|
|||||||
* An object class used to pass some parameters
|
* An object class used to pass some parameters
|
||||||
* that exists only on native targets
|
* that exists only on native targets
|
||||||
*/
|
*/
|
||||||
public expect class PingContext()
|
public expect class MCPingContext()
|
||||||
@@ -8,14 +8,14 @@
|
|||||||
package cn.rtast.mcping.platform
|
package cn.rtast.mcping.platform
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _Socket public constructor(host: String, port: Int, context: PingContext) {
|
public expect class _Socket public constructor(host: String, port: Int, context: MCPingContext) {
|
||||||
public fun openReadChannel(): _ReadChannel
|
public fun openReadChannel(): _ReadChannel
|
||||||
public fun openWriteChannel(): _WriteChannel
|
public fun openWriteChannel(): _WriteChannel
|
||||||
public fun close()
|
public fun close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public expect class _UdpSocket public constructor(host: String, port: Int, context: PingContext) {
|
public expect class _UdpSocket public constructor(host: String, port: Int, context: MCPingContext) {
|
||||||
public fun sendAndReceive(data: ByteArray): ByteArray
|
public fun sendAndReceive(data: ByteArray): ByteArray
|
||||||
public fun close()
|
public fun close()
|
||||||
}
|
}
|
||||||
@@ -24,21 +24,37 @@ public actual class _Buffer {
|
|||||||
outStream.write(value.toInt())
|
outStream.write(value.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun writeShort(value: Short) {
|
public actual fun writeShort(value: Short, endian: ByteOrder) {
|
||||||
val v = value.toInt()
|
val v = value.toInt()
|
||||||
outStream.write(v shr 8)
|
if (endian == ByteOrder.BIG_ENDIAN) {
|
||||||
outStream.write(v)
|
outStream.write(v shr 8)
|
||||||
|
outStream.write(v)
|
||||||
|
} else {
|
||||||
|
outStream.write(v)
|
||||||
|
outStream.write(v shr 8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun writeLong(value: Long) {
|
public actual fun writeInt(value: Int, endian: ByteOrder) {
|
||||||
outStream.write((value shr 56).toInt())
|
if (endian == ByteOrder.BIG_ENDIAN) {
|
||||||
outStream.write((value shr 48).toInt())
|
outStream.write(value shr 24)
|
||||||
outStream.write((value shr 40).toInt())
|
outStream.write(value shr 16)
|
||||||
outStream.write((value shr 32).toInt())
|
outStream.write(value shr 8)
|
||||||
outStream.write((value shr 24).toInt())
|
outStream.write(value)
|
||||||
outStream.write((value shr 16).toInt())
|
} else {
|
||||||
outStream.write((value shr 8).toInt())
|
outStream.write(value)
|
||||||
outStream.write(value.toInt())
|
outStream.write(value shr 8)
|
||||||
|
outStream.write(value shr 16)
|
||||||
|
outStream.write(value shr 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun writeLong(value: Long, endian: ByteOrder) {
|
||||||
|
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 fun writeBytes(bytes: ByteArray) {
|
public actual fun writeBytes(bytes: ByteArray) {
|
||||||
@@ -60,23 +76,9 @@ public actual class _Buffer {
|
|||||||
return array[readOffset++]
|
return array[readOffset++]
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun readShort(): Short {
|
public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
|
||||||
val b1 = readByte().toInt() and 0xFF
|
public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
|
||||||
val b2 = readByte().toInt() and 0xFF
|
public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
|
||||||
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 {
|
public actual fun readBytes(length: Int): ByteArray {
|
||||||
val array = ensureReadArray()
|
val array = ensureReadArray()
|
||||||
if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow")
|
if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow")
|
||||||
@@ -87,6 +89,6 @@ public actual class _Buffer {
|
|||||||
|
|
||||||
public actual fun toByteArray(): ByteArray = outStream.toByteArray()
|
public actual fun toByteArray(): ByteArray = outStream.toByteArray()
|
||||||
|
|
||||||
public actual val size: Int
|
public actual fun close(): Unit = outStream.close()
|
||||||
get() = outStream.size()
|
public actual val size: Int get() = outStream.size()
|
||||||
}
|
}
|
||||||
@@ -12,15 +12,11 @@ import java.io.InputStream
|
|||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _ReadChannel {
|
public actual class _ReadChannel(private val _inputStream: InputStream) {
|
||||||
private val _inputStream: InputStream
|
|
||||||
|
|
||||||
public constructor(inputStream: InputStream) {
|
|
||||||
_inputStream = inputStream
|
|
||||||
}
|
|
||||||
|
|
||||||
public actual fun readByte(): Byte = _inputStream.read().toByte()
|
public actual fun readByte(): Byte = _inputStream.read().toByte()
|
||||||
public actual fun readBytes(length: Int): ByteArray = _inputStream.readNBytes(length)
|
public actual fun readBytes(length: Int): ByteArray = _inputStream.readNBytes(length)
|
||||||
|
|
||||||
public actual fun readFully(out: ByteArray, start: Int, end: Int) {
|
public actual fun readFully(out: ByteArray, start: Int, end: Int) {
|
||||||
var bytesRead = 0
|
var bytesRead = 0
|
||||||
val length = end - start
|
val length = end - start
|
||||||
@@ -31,23 +27,9 @@ public actual class _ReadChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun readLong(): Long {
|
public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
|
||||||
val bytes = ByteArray(8)
|
public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
|
||||||
var read = 0
|
public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
|
||||||
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")
|
@Suppress("CLASSNAME")
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ package cn.rtast.mcping.platform
|
|||||||
/**
|
/**
|
||||||
* No Context for jvm targets
|
* No Context for jvm targets
|
||||||
*/
|
*/
|
||||||
public actual class PingContext
|
public actual class MCPingContext
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.mcping.platform
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import java.net.InetSocketAddress
|
|||||||
import java.net.Socket as JvmSocket
|
import java.net.Socket as JvmSocket
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _Socket public actual constructor(host: String, port: Int, context: PingContext) {
|
public actual class _Socket public actual constructor(host: String, port: Int, context: MCPingContext) {
|
||||||
private val socket = JvmSocket(host, port)
|
private val socket = JvmSocket(host, port)
|
||||||
|
|
||||||
public actual fun openReadChannel(): _ReadChannel = _ReadChannel(socket.getInputStream())
|
public actual fun openReadChannel(): _ReadChannel = _ReadChannel(socket.getInputStream())
|
||||||
@@ -21,7 +21,7 @@ public actual class _Socket public actual constructor(host: String, port: Int, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: PingContext) {
|
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: MCPingContext) {
|
||||||
private val socket = DatagramSocket().apply {
|
private val socket = DatagramSocket().apply {
|
||||||
soTimeout = 3000
|
soTimeout = 3000
|
||||||
connect(InetSocketAddress(host, port))
|
connect(InetSocketAddress(host, port))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package cn.rtast.mcping.platform
|
package cn.rtast.mcping.platform
|
||||||
|
|
||||||
|
import io.ktor.utils.io.bits.*
|
||||||
import kotlinx.io.Buffer
|
import kotlinx.io.Buffer
|
||||||
import kotlinx.io.readByteArray
|
import kotlinx.io.readByteArray
|
||||||
|
|
||||||
@@ -22,16 +23,41 @@ public actual class _Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value)
|
public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value)
|
||||||
public actual fun writeShort(value: Short): Unit = _delegateBuf.writeShort(value)
|
public actual fun writeShort(value: Short, endian: ByteOrder) {
|
||||||
public actual fun writeLong(value: Long): Unit = _delegateBuf.writeLong(value)
|
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeShort(value)
|
||||||
|
else _delegateBuf.writeShort(value.reverseByteOrder())
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun writeInt(value: Int, endian: ByteOrder) {
|
||||||
|
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeInt(value)
|
||||||
|
else _delegateBuf.writeInt(value.reverseByteOrder())
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun writeLong(value: Long, endian: ByteOrder) {
|
||||||
|
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeLong(value)
|
||||||
|
else _delegateBuf.writeLong(value.reverseByteOrder())
|
||||||
|
}
|
||||||
|
|
||||||
public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes)
|
public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes)
|
||||||
|
|
||||||
public actual fun readByte(): Byte = _delegateBuf.readByte()
|
public actual fun readByte(): Byte = _delegateBuf.readByte()
|
||||||
public actual fun readShort(): Short = _delegateBuf.readShort()
|
public actual fun readShort(endian: ByteOrder): Short {
|
||||||
public actual fun readLong(): Long = _delegateBuf.readLong()
|
val v = _delegateBuf.readShort()
|
||||||
public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
|
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun readInt(endian: ByteOrder): Int {
|
||||||
|
val v = _delegateBuf.readInt()
|
||||||
|
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun readLong(endian: ByteOrder): Long {
|
||||||
|
val v = _delegateBuf.readLong()
|
||||||
|
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
|
||||||
public actual fun toByteArray(): ByteArray = _delegateBuf.peek().readByteArray()
|
public actual fun toByteArray(): ByteArray = _delegateBuf.peek().readByteArray()
|
||||||
|
public actual fun close(): Unit = _delegateBuf.close()
|
||||||
|
|
||||||
public actual val size: Int
|
public actual val size: Int
|
||||||
get() = _delegateBuf.size.toInt()
|
get() = _delegateBuf.size.toInt()
|
||||||
|
|||||||
@@ -7,22 +7,31 @@
|
|||||||
package cn.rtast.mcping.platform
|
package cn.rtast.mcping.platform
|
||||||
|
|
||||||
import io.ktor.utils.io.*
|
import io.ktor.utils.io.*
|
||||||
|
import io.ktor.utils.io.bits.reverseByteOrder
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _ReadChannel {
|
public actual class _ReadChannel(private val _readChannel: ByteReadChannel) {
|
||||||
private val _readChannel: ByteReadChannel
|
|
||||||
|
|
||||||
public constructor(readChannel: ByteReadChannel) {
|
|
||||||
_readChannel = readChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
|
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
|
||||||
public actual fun readBytes(length: Int): ByteArray = runBlocking { _readChannel.readByteArray(length) }
|
public actual fun readBytes(length: Int): ByteArray = runBlocking { _readChannel.readByteArray(length) }
|
||||||
public actual fun readFully(out: ByteArray, start: Int, end: Int): Unit =
|
public actual fun readFully(out: ByteArray, start: Int, end: Int): Unit =
|
||||||
runBlocking { _readChannel.readFully(out, start, end) }
|
runBlocking { _readChannel.readFully(out, start, end) }
|
||||||
|
|
||||||
public actual fun readLong(): Long = runBlocking { _readChannel.readLong() }
|
public actual fun readShort(endian: ByteOrder): Short = runBlocking {
|
||||||
|
val v = _readChannel.readShort()
|
||||||
|
if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun readInt(endian: ByteOrder): Int = runBlocking {
|
||||||
|
val v = _readChannel.readInt()
|
||||||
|
if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
public actual fun readLong(endian: ByteOrder): Long = runBlocking {
|
||||||
|
val v = _readChannel.readLong()
|
||||||
|
if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import io.ktor.network.selector.*
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
|
|
||||||
public actual class PingContext actual constructor() {
|
public actual class MCPingContext actual constructor() {
|
||||||
internal var _selectorManager: SelectorManager = SelectorManager(Dispatchers.IO)
|
internal var _selectorManager: SelectorManager = SelectorManager(Dispatchers.IO)
|
||||||
internal var _autoCloseSelectorManager: Boolean = false
|
internal var _autoCloseSelectorManager: Boolean = false
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.io.readByteArray
|
import kotlinx.io.readByteArray
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _Socket public actual constructor(host: String, port: Int, context: PingContext) {
|
public actual class _Socket public actual constructor(host: String, port: Int, context: MCPingContext) {
|
||||||
private val ctx = context
|
private val ctx = context
|
||||||
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
|
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ public actual class _Socket public actual constructor(host: String, port: Int, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CLASSNAME")
|
@Suppress("CLASSNAME")
|
||||||
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: PingContext) {
|
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: MCPingContext) {
|
||||||
private val ctx = context
|
private val ctx = context
|
||||||
|
|
||||||
// use bind to create an unconnected socket
|
// use bind to create an unconnected socket
|
||||||
|
|||||||
+1
-1
@@ -4,4 +4,4 @@ kotlin.native.enableKlibsCrossCompilation=true
|
|||||||
kotlin.daemon.jvmargs=-Xmx2048M
|
kotlin.daemon.jvmargs=-Xmx2048M
|
||||||
org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8
|
||||||
|
|
||||||
libVersion=0.0.2
|
libVersion=0.0.3
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
package cn.rtast.mcping.bedrock
|
package cn.rtast.mcping.bedrock
|
||||||
|
|
||||||
import cn.rtast.mcping.PingResponse
|
import cn.rtast.mcping.PingResponse
|
||||||
import cn.rtast.mcping.platform.PingContext
|
import cn.rtast.mcping.platform.MCPingContext
|
||||||
import cn.rtast.mcping.platform._UdpSocket
|
import cn.rtast.mcping.platform._UdpSocket
|
||||||
import cn.rtast.mcping.platform.wrap
|
import cn.rtast.mcping.platform.wrap
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
internal fun pingBedrockServer(host: String, port: Int, context: PingContext): PingResponse {
|
internal fun pingBedrockServer(host: String, port: Int, context: MCPingContext): PingResponse {
|
||||||
val socket = _UdpSocket(host, port, context)
|
val socket = _UdpSocket(host, port, context)
|
||||||
return try {
|
return try {
|
||||||
val sendTime = Clock.System.now().toEpochMilliseconds()
|
val sendTime = Clock.System.now().toEpochMilliseconds()
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
package cn.rtast.mcping.java
|
package cn.rtast.mcping.java
|
||||||
|
|
||||||
import cn.rtast.mcping.PingResponse
|
import cn.rtast.mcping.PingResponse
|
||||||
import cn.rtast.mcping.platform.PingContext
|
import cn.rtast.mcping.platform.MCPingContext
|
||||||
import cn.rtast.mcping.platform._Socket
|
import cn.rtast.mcping.platform._Socket
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
internal fun pingJavaServer(host: String, port: Int, context: PingContext): PingResponse {
|
internal fun pingJavaServer(host: String, port: Int, context: MCPingContext): PingResponse {
|
||||||
val socket = _Socket(host, port, context)
|
val socket = _Socket(host, port, context)
|
||||||
val receiveChannel = socket.openReadChannel()
|
val receiveChannel = socket.openReadChannel()
|
||||||
val sendChannel = socket.openWriteChannel()
|
val sendChannel = socket.openWriteChannel()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ package cn.rtast.mcping
|
|||||||
|
|
||||||
import cn.rtast.mcping.bedrock.pingBedrockServer
|
import cn.rtast.mcping.bedrock.pingBedrockServer
|
||||||
import cn.rtast.mcping.java.pingJavaServer
|
import cn.rtast.mcping.java.pingJavaServer
|
||||||
import cn.rtast.mcping.platform.PingContext
|
import cn.rtast.mcping.platform.MCPingContext
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
import kotlin.jvm.JvmOverloads
|
import kotlin.jvm.JvmOverloads
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public fun mcping(
|
|||||||
host: String,
|
host: String,
|
||||||
port: Int,
|
port: Int,
|
||||||
type: ServerType = ServerType.Java,
|
type: ServerType = ServerType.Java,
|
||||||
context: PingContext = PingContext(),
|
context: MCPingContext = MCPingContext(),
|
||||||
): PingResponse = when (type) {
|
): PingResponse = when (type) {
|
||||||
ServerType.Java -> pingJavaServer(host, port, context)
|
ServerType.Java -> pingJavaServer(host, port, context)
|
||||||
ServerType.Bedrock -> pingBedrockServer(host, port, context)
|
ServerType.Bedrock -> pingBedrockServer(host, port, context)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ package test
|
|||||||
|
|
||||||
import cn.rtast.mcping.ServerType
|
import cn.rtast.mcping.ServerType
|
||||||
import cn.rtast.mcping.mcping
|
import cn.rtast.mcping.mcping
|
||||||
import cn.rtast.mcping.platform.PingContext
|
import cn.rtast.mcping.platform.MCPingContext
|
||||||
import io.ktor.network.selector.*
|
import io.ktor.network.selector.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
@@ -22,7 +22,7 @@ class TestMingwPing {
|
|||||||
fun `test ping java on mingw with selectorManager`() = runTest {
|
fun `test ping java on mingw with selectorManager`() = runTest {
|
||||||
val sm = SelectorManager(Dispatchers.IO)
|
val sm = SelectorManager(Dispatchers.IO)
|
||||||
val response =
|
val response =
|
||||||
mcping(host = "org.mc-complex.com", port = 25565, type = ServerType.Java, context = PingContext(sm))
|
mcping(host = "org.mc-complex.com", port = 25565, type = ServerType.Java, context = MCPingContext(sm))
|
||||||
println(response)
|
println(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class TestMingwPing {
|
|||||||
fun `test ping bedrock on mingw with selectorManager`() = runTest {
|
fun `test ping bedrock on mingw with selectorManager`() = runTest {
|
||||||
val sm = SelectorManager(Dispatchers.IO)
|
val sm = SelectorManager(Dispatchers.IO)
|
||||||
val response =
|
val response =
|
||||||
mcping(host = "play.wildnetwork.net", port = 19132, type = ServerType.Bedrock, context = PingContext(sm))
|
mcping(host = "play.wildnetwork.net", port = 19132, type = ServerType.Bedrock, context = MCPingContext(sm))
|
||||||
println(response)
|
println(response)
|
||||||
println(response.toBedrockResponse())
|
println(response.toBedrockResponse())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ kotlin {
|
|||||||
implementation(project(":common"))
|
implementation(project(":common"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jvmMain.dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.mcping.rconlib
|
||||||
|
|
||||||
|
import cn.rtast.mcping.platform._Buffer
|
||||||
|
|
||||||
|
internal fun _Buffer.writeNull() = writeByte(0x00)
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.mcping.rconlib
|
||||||
|
|
||||||
|
import cn.rtast.mcping.platform.ByteOrder
|
||||||
|
import cn.rtast.mcping.platform._Buffer
|
||||||
|
import cn.rtast.mcping.platform._ReadChannel
|
||||||
|
import cn.rtast.mcping.platform._WriteChannel
|
||||||
|
|
||||||
|
internal abstract class Packet {
|
||||||
|
abstract val requestId: Int
|
||||||
|
abstract val type: Int
|
||||||
|
abstract val payload: String
|
||||||
|
|
||||||
|
protected val payloadBytes: ByteArray by lazy { payload.encodeToByteArray() }
|
||||||
|
|
||||||
|
val payloadLength: Int
|
||||||
|
get() = payloadBytes.size
|
||||||
|
|
||||||
|
open val length: Int
|
||||||
|
get() = 4 + 4 + payloadLength + 2
|
||||||
|
|
||||||
|
fun writePayload(buf: _Buffer) {
|
||||||
|
buf.writeInt(length, ByteOrder.LITTLE_ENDIAN)
|
||||||
|
buf.writeInt(requestId, ByteOrder.LITTLE_ENDIAN)
|
||||||
|
buf.writeInt(type, ByteOrder.LITTLE_ENDIAN)
|
||||||
|
buf.writeBytes(payloadBytes)
|
||||||
|
buf.writeNull()
|
||||||
|
buf.writeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Codec {
|
||||||
|
fun decode(channel: _ReadChannel): ResponsePacket {
|
||||||
|
val length = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
val requestId = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
val type = channel.readInt(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
val payloadBuffer = _Buffer()
|
||||||
|
while (true) {
|
||||||
|
val b = channel.readByte()
|
||||||
|
if (b == 0x00.toByte()) break
|
||||||
|
payloadBuffer.writeByte(b)
|
||||||
|
}
|
||||||
|
channel.readByte() // consume last null byte
|
||||||
|
return ResponsePacket(length, requestId, type, payloadBuffer.toByteArray().decodeToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class AuthPacket(
|
||||||
|
override val payload: String,
|
||||||
|
override val requestId: Int = 1,
|
||||||
|
) : Packet() {
|
||||||
|
override val type: Int = PacketType.AUTH
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class ExecCommandPacket(
|
||||||
|
override val payload: String,
|
||||||
|
override val requestId: Int,
|
||||||
|
) : Packet() {
|
||||||
|
override val type: Int = PacketType.EXEC_COMMAND
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class ResponsePacket(
|
||||||
|
override val length: Int,
|
||||||
|
override val requestId: Int,
|
||||||
|
override val type: Int,
|
||||||
|
override val payload: String,
|
||||||
|
) : Packet()
|
||||||
|
|
||||||
|
|
||||||
|
internal fun _WriteChannel.sendPacket(packet: Packet) {
|
||||||
|
val buf = _Buffer()
|
||||||
|
packet.writePayload(buf)
|
||||||
|
writeFully(buf.toByteArray())
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun _ReadChannel.readPacket(): ResponsePacket = Packet.decode(this)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.mcping.rconlib
|
||||||
|
|
||||||
|
internal object PacketType {
|
||||||
|
const val AUTH = 3
|
||||||
|
const val EXEC_COMMAND = 2
|
||||||
|
const val RESPONSE = 1
|
||||||
|
}
|
||||||
@@ -4,6 +4,51 @@
|
|||||||
* Date: 2026/9/4
|
* Date: 2026/9/4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:JvmName("Rconlib")
|
||||||
|
|
||||||
package cn.rtast.mcping.rconlib
|
package cn.rtast.mcping.rconlib
|
||||||
|
|
||||||
|
import cn.rtast.mcping.platform.MCPingContext
|
||||||
|
import cn.rtast.mcping.platform._ReadChannel
|
||||||
|
import cn.rtast.mcping.platform._Socket
|
||||||
|
import cn.rtast.mcping.platform._WriteChannel
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
import kotlin.jvm.JvmOverloads
|
||||||
|
|
||||||
|
public class RCONClient internal constructor(
|
||||||
|
private val host: String,
|
||||||
|
private val port: Int,
|
||||||
|
private val context: MCPingContext,
|
||||||
|
) : AutoCloseable {
|
||||||
|
private var socket: _Socket? = null
|
||||||
|
private var readChannel: _ReadChannel? = null
|
||||||
|
private var writeChannel: _WriteChannel? = null
|
||||||
|
private var currentRequestId = 1
|
||||||
|
|
||||||
|
public fun connect(password: String): Boolean {
|
||||||
|
socket = _Socket(host, port, context)
|
||||||
|
readChannel = socket!!.openReadChannel()
|
||||||
|
writeChannel = socket!!.openWriteChannel()
|
||||||
|
val authPacket = AuthPacket(password, currentRequestId)
|
||||||
|
writeChannel!!.sendPacket(authPacket)
|
||||||
|
return readChannel!!.readPacket().requestId != -1
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun command(command: String): String {
|
||||||
|
val reqId = currentRequestId++
|
||||||
|
val commandPacket = ExecCommandPacket(command, reqId)
|
||||||
|
writeChannel!!.sendPacket(commandPacket)
|
||||||
|
return readChannel!!.readPacket().payload
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun close() {
|
||||||
|
socket?.close()
|
||||||
|
socket = null
|
||||||
|
readChannel = null
|
||||||
|
writeChannel = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
public fun rconClient(host: String, port: Int, context: MCPingContext = MCPingContext()): RCONClient =
|
||||||
|
RCONClient(host, port, context)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import cn.rtast.mcping.rconlib.rconClient
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TestRCON {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test rcon client`() {
|
||||||
|
val host = "127.0.0.1"
|
||||||
|
val port = 25575
|
||||||
|
val client = rconClient(host, port)
|
||||||
|
val authed = client.connect("123456")
|
||||||
|
if (authed) println(client.command("list")) else throw IllegalArgumentException("incorrect password")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/4
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package test;
|
||||||
|
|
||||||
|
import cn.rtast.mcping.rconlib.RCONClient;
|
||||||
|
import cn.rtast.mcping.rconlib.Rconlib;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestRconClientOnJava {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRconClient() {
|
||||||
|
String host = "127.0.0.1";
|
||||||
|
int port = 25575;
|
||||||
|
String password = "123456";
|
||||||
|
RCONClient rconClient = Rconlib.rconClient(host, port);
|
||||||
|
boolean authed = rconClient.connect(password);
|
||||||
|
if (authed) {
|
||||||
|
System.out.println(rconClient.command("list"));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("incorrect password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user