Initial commit
This commit is contained in:
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()
|
||||
}
|
||||
Reference in New Issue
Block a user