Initial commit

This commit is contained in:
2026-09-03 19:32:46 +08:00
commit b3583ba48d
25 files changed
+1411

No files matched your search

@@ -0,0 +1,48 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping
import cn.rtast.mcping.platform.PlatformBuffer
import cn.rtast.mcping.platform.PlatformReadChannel
internal fun PlatformBuffer.writeVarInt(value: Int) {
var v = value
while (true) {
if ((v and 0x7F.inv()) == 0) {
this.writeByte(v.toByte())
return
}
this.writeByte(((v and 0x7F) or 0x80).toByte())
v = v ushr 7
}
}
internal fun PlatformReadChannel.readVarInt(): Int {
var value = 0
var position = 0
while (true) {
val currentByte = this.readByte().toInt() and 0xFF
value = value or ((currentByte and 0x7F) shl position)
if ((currentByte and 0x80) == 0) break
position += 7
if (position >= 35) throw IllegalArgumentException("VarInt too long")
}
return value
}
internal fun PlatformBuffer.writeMcString(value: String) {
val bytes = value.encodeToByteArray()
this.writeVarInt(bytes.size)
this.writeBytes(bytes)
}
internal fun PlatformReadChannel.readMcString(): String {
val length = this.readVarInt()
val bytes = this.readBytes(length)
return bytes.decodeToString()
}
@@ -0,0 +1,35 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping
import cn.rtast.mcping.platform.PlatformSocket
public fun mcping(host: String, port: Int): String {
val socket = PlatformSocket(host, port)
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)
sendChannel.sendPacket(StatusRequestPacket)
receiveChannel.readVarInt() // consume a varint
val packetId = receiveChannel.readVarInt()
if (packetId == StatusRequestPacket.packetId) receiveChannel.readMcString()
else throw IllegalStateException("Server does not respond correct packet id, expected ${StatusRequestPacket.packetId} but got $packetId")
} finally {
socket.close()
}
}
@@ -0,0 +1,43 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping
import cn.rtast.mcping.platform.PlatformBuffer
internal interface MinecraftPacket {
val packetId: Int
fun writePayload(buffer: PlatformBuffer)
}
// 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
val nextState: Int,
) : MinecraftPacket {
override val packetId: Int = 0x00
override fun writePayload(buffer: PlatformBuffer) {
buffer.writeVarInt(protocolVersion)
buffer.writeMcString(serverAddress)
// write UShort
// write 2 bytes big endian
buffer.writeByte((serverPort.toInt() shr 8).toByte())
buffer.writeByte(serverPort.toByte())
buffer.writeVarInt(nextState)
}
}
// ref https://minecraft.wiki/w/Java_Edition_protocol/Packets#Status
internal data object StatusRequestPacket : MinecraftPacket {
override val packetId: Int = 0x00
override fun writePayload(buffer: PlatformBuffer) {}
}
@@ -0,0 +1,23 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping
import cn.rtast.mcping.platform.PlatformBuffer
import cn.rtast.mcping.platform.PlatformWriteChannel
internal fun PlatformWriteChannel.sendPacket(packet: MinecraftPacket) {
val bodyBuffer = PlatformBuffer()
bodyBuffer.writeVarInt(packet.packetId)
packet.writePayload(bodyBuffer)
val frameBuffer = PlatformBuffer()
frameBuffer.writeVarInt(bodyBuffer.size)
frameBuffer.writeBytes(bodyBuffer.toByteArray())
val bytes = frameBuffer.toByteArray()
this.writeFully(bytes, 0, bytes.size)
this.flush()
}
@@ -0,0 +1,17 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
internal expect class PlatformBuffer() {
fun writeByte(value: Byte)
fun writeBytes(bytes: ByteArray)
fun readByte(): Byte
fun readBytes(length: Int): ByteArray
fun toByteArray(): ByteArray
val size: Int
}
@@ -0,0 +1,19 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
internal expect class PlatformReadChannel {
fun readByte(): Byte
fun readBytes(length: Int): ByteArray
fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
}
internal expect class PlatformWriteChannel {
fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size)
fun flush()
}
@@ -0,0 +1,14 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/3
*/
package cn.rtast.mcping.platform
internal expect class PlatformSocket internal constructor(host: String, port: Int){
fun openReadChannel(): PlatformReadChannel
fun openWriteChannel(): PlatformWriteChannel
fun close()
}