mucking around with sockets and rcon
This commit is contained in:
parent
17b4fc3b9a
commit
c3ac3bd34a
12 changed files with 613 additions and 4 deletions
15
modules/kt-rcon/build.gradle.kts
Normal file
15
modules/kt-rcon/build.gradle.kts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
id("dev.adamko.factoriowebmap.archetype.kotlin-jvm")
|
||||
}
|
||||
|
||||
//group = "${rootProject.group}.server"
|
||||
//version = rootProject.version
|
||||
//
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
|
||||
|
||||
implementation("io.ktor:ktor-network:1.6.4")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package dev.adamko.ktrcon
|
||||
|
||||
import java.io.*
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import java.nio.BufferUnderflowException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.channels.AsynchronousSocketChannel
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.yield
|
||||
|
||||
class KtRconClient(
|
||||
private val host: String,
|
||||
private val port: Int,
|
||||
private val password: Password,
|
||||
) : AutoCloseable {
|
||||
|
||||
private val rconClientScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
private val socket: Socket = Socket(host, port)
|
||||
|
||||
init {
|
||||
|
||||
runBlocking {
|
||||
send(PacketType.AUTH_REQUEST, password())
|
||||
}
|
||||
|
||||
require(socket.isConnected) { "Socket must connect" }
|
||||
|
||||
println("socket.isConnected: ${socket.isConnected}")
|
||||
|
||||
// val j = rconClientScope.launch {
|
||||
|
||||
runCatching {
|
||||
val reader = socket.getInputStream()
|
||||
.bufferedReader()
|
||||
.lineSequence()
|
||||
.asFlow()
|
||||
.onEach {
|
||||
println("socket response: [$it]")
|
||||
}
|
||||
.launchIn(rconClientScope)
|
||||
|
||||
// while (socket.isConnected) {
|
||||
// println("response: ${input.read().toByte().toInt().toChar()}")
|
||||
|
||||
println("is listener active? ${reader.isActive}")
|
||||
|
||||
reader.invokeOnCompletion {
|
||||
println("cancelled input stream job")
|
||||
}
|
||||
|
||||
}
|
||||
// }
|
||||
// yield()
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
suspend fun send(type: PacketType, body: String) = supervisorScope {
|
||||
|
||||
val p = Packet(Random.nextUInt(), type, body)
|
||||
|
||||
println("Sending packet: $p - totalSize: ${p.totalSize}")
|
||||
println("p.size: ${p.size}")
|
||||
println("p.id: ${p.id}")
|
||||
println("p.type.value: ${p.type.value}")
|
||||
// println("p.bodyBytes: ${p.bodyBytes} / ${p.body}")
|
||||
// println("p.terminator: ${p.terminator} / ${p.terminator.decodeToString()}")
|
||||
|
||||
// println("p.byteArray: ${p.toByteArray()}")
|
||||
|
||||
val bb = p.toByteBuffer2()
|
||||
// ByteBuffer.wrap(p.toByteArray())
|
||||
// bb.order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
// val bb = ByteBuffer.allocate(p.totalSize * 2).apply {
|
||||
// order(ByteOrder.LITTLE_ENDIAN)
|
||||
// putInt(p.size)
|
||||
// putInt(p.id.toInt())
|
||||
// putInt(p.type.value)
|
||||
// put(p.bodyBytes)
|
||||
// put(p.terminator)
|
||||
// }
|
||||
|
||||
runCatching {
|
||||
socket.getOutputStream().use { os ->
|
||||
os.write(bb.array())
|
||||
os.flush()
|
||||
}
|
||||
}
|
||||
// socket.channel.finishConnect()
|
||||
// socket.getInputStream().use { input ->
|
||||
// println("running inputddd")
|
||||
//// while (true) {
|
||||
// println("response: ${input.readAllBytes().decodeToString()}")
|
||||
//
|
||||
//// yield()
|
||||
//// }
|
||||
// }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
rconClientScope.cancel("Closing ${this::class}")
|
||||
socket.close()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package dev.adamko.ktrcon
|
||||
|
||||
import java.io.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import java.nio.BufferUnderflowException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.channels.AsynchronousSocketChannel
|
||||
import java.nio.channels.CompletionHandler
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.yield
|
||||
|
||||
class KtRconClient2(
|
||||
private val host: String,
|
||||
private val port: Int,
|
||||
private val password: Password,
|
||||
) : AutoCloseable {
|
||||
|
||||
private val rconClientScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
private val client: AsynchronousSocketChannel by lazy {
|
||||
println("Initialising client")
|
||||
|
||||
val client = AsynchronousSocketChannel.open()
|
||||
val address = InetSocketAddress(host, port)
|
||||
val result = client.connect(address)
|
||||
result.get() //.get(2, TimeUnit.SECONDS)
|
||||
|
||||
println("connected to async - $address")
|
||||
require(client.isOpen) { "Require client is open" }
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
init {
|
||||
}
|
||||
|
||||
suspend fun send(type: PacketType, body: String) = supervisorScope {
|
||||
|
||||
val p = Packet(Random.nextUInt(1u..100u), type, body)
|
||||
|
||||
println("Sending packet: $p - totalSize: ${p.totalSize}")
|
||||
println("p.size: ${p.size}")
|
||||
println("p.totalSize: ${p.totalSize}")
|
||||
println("p.id: ${p.id}")
|
||||
println("p.type.value: ${p.type.value}")
|
||||
// println("p.bodyBytes: ${p.bodyBytes} / ${p.body}")
|
||||
// println("p.terminator: ${p.terminator} / ${p.terminator.decodeToString()}")
|
||||
|
||||
// println("p.byteArray: ${p.toByteArray()}")
|
||||
// println("p.byteArray: ${p.toByteBuffer().array()}")
|
||||
|
||||
|
||||
runCatching {
|
||||
val bb = p.toByteBuffer2()
|
||||
// ByteBuffer.wrap(p.toByteArray())
|
||||
// bb.order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
println("writing bb to client")
|
||||
bb.compact()
|
||||
val future = client.write(bb)
|
||||
|
||||
delay(2000)
|
||||
|
||||
val writeResult = future.get() //.get(2, TimeUnit.SECONDS)
|
||||
delay(2000)
|
||||
|
||||
println("write result: $writeResult")
|
||||
|
||||
|
||||
val response = ByteBuffer.allocate(Int.SIZE_BYTES)
|
||||
val responseStatus = runCatching {
|
||||
client.read(response).get() //.get(2, TimeUnit.SECONDS)
|
||||
}
|
||||
println("responseStatus: $responseStatus")
|
||||
println("response from server: ${response.array().decodeToString()}")
|
||||
delay(2000)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
rconClientScope.cancel("Closing ${this::class}")
|
||||
client.shutdownInput()
|
||||
client.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package dev.adamko.ktrcon
|
||||
|
||||
import io.ktor.network.selector.ActorSelectorManager
|
||||
import io.ktor.network.sockets.aSocket
|
||||
import io.ktor.network.sockets.openReadChannel
|
||||
import io.ktor.network.sockets.openWriteChannel
|
||||
import io.ktor.util.decodeString
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import io.ktor.utils.io.ByteWriteChannel
|
||||
import io.ktor.utils.io.core.isNotEmpty
|
||||
import io.ktor.utils.io.core.readBytes
|
||||
import io.ktor.utils.io.readRemaining
|
||||
import io.ktor.utils.io.readUTF8Line
|
||||
import io.ktor.utils.io.writeIntLittleEndian
|
||||
import io.ktor.utils.io.writeStringUtf8
|
||||
import java.io.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import java.nio.BufferUnderflowException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.channels.AsynchronousSocketChannel
|
||||
import java.nio.channels.CompletionHandler
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.yield
|
||||
|
||||
class KtRconClient3(
|
||||
private val host: String,
|
||||
private val port: Int,
|
||||
private val password: Password,
|
||||
) : AutoCloseable {
|
||||
|
||||
private val rconClientJob = SupervisorJob()
|
||||
private val rconClientScope = CoroutineScope(rconClientJob)
|
||||
|
||||
private val input: ByteReadChannel
|
||||
private val output: ByteWriteChannel
|
||||
|
||||
init {
|
||||
val socket = runBlocking {
|
||||
val selector = ActorSelectorManager(rconClientJob)
|
||||
aSocket(selector)
|
||||
.tcp()
|
||||
.connect(InetSocketAddress(host, port))
|
||||
}
|
||||
input = socket.openReadChannel()
|
||||
output = socket.openWriteChannel()
|
||||
|
||||
// runBlocking {
|
||||
// output.writeStringUtf8("test")
|
||||
// output.flush()
|
||||
// delay(1000)
|
||||
// println("availableForRead: ${input.availableForRead}")
|
||||
//
|
||||
// }
|
||||
|
||||
rconClientScope.launch {
|
||||
while (!input.isClosedForRead) {
|
||||
val packet = input.readRemaining(1)
|
||||
while (packet.isNotEmpty) {
|
||||
val bytes = packet.readBytes()
|
||||
println(
|
||||
"received ${bytes.size} bytes: ${bytes.joinToString { "$it" }} - ${bytes.decodeToString()}"
|
||||
)
|
||||
yield()
|
||||
}
|
||||
yield()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun send(type: PacketType, body: String) = supervisorScope {
|
||||
|
||||
val p = Packet(Random.nextUInt(1u..100u), type, body)
|
||||
|
||||
println("Sending packet: $p - totalSize: ${p.totalSize}")
|
||||
println("p.size: ${p.size}")
|
||||
println("p.totalSize: ${p.totalSize}")
|
||||
println("p.id: ${p.id}")
|
||||
println("p.type.value: ${p.type.value}")
|
||||
// println("p.bodyBytes: ${p.bodyBytes} / ${p.body}")
|
||||
// println("p.terminator: ${p.terminator} / ${p.terminator.decodeToString()}")
|
||||
|
||||
// println("p.byteArray: ${p.toByteArray()}")
|
||||
println("p.byteArray: ${p.toByteBuffer2().array().joinToString { it.toString() }}")
|
||||
|
||||
|
||||
val bb = p.toByteBuffer2()
|
||||
println("bb: ${bb.position()} - ${bb.toString()}")
|
||||
|
||||
bb.array().forEach {
|
||||
print("-$it")
|
||||
output.writeByte(it)
|
||||
}
|
||||
// output.writeFully(bb)
|
||||
// output.writeFully(bb)
|
||||
output.flush()
|
||||
println("finished writing")
|
||||
|
||||
// if (type == PacketType.EXEC_COMMAND) {
|
||||
// println("awaiting response from server")
|
||||
// input.read(0) {
|
||||
// println("server response: ${it.array()}")
|
||||
// }
|
||||
//// val response = input.readUTF8Line()
|
||||
//// println("Server said $response")
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
rconClientJob.complete()
|
||||
rconClientScope.cancel("Closing ${this::class}")
|
||||
}
|
||||
|
||||
}
|
||||
152
modules/kt-rcon/src/main/kotlin/dev/adamko/ktrcon/Packet.kt
Normal file
152
modules/kt-rcon/src/main/kotlin/dev/adamko/ktrcon/Packet.kt
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package dev.adamko.ktrcon
|
||||
|
||||
import io.ktor.utils.io.bits.reverseByteOrder
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
|
||||
/**
|
||||
* @param[size] The packet size field is a 32-bit little endian integer, representing the length of the
|
||||
* request in bytes. Note that the packet size field itself is not included when determining the
|
||||
* size of the packet, so the value of this field is always 4 less than the packet's actual
|
||||
* length.
|
||||
*
|
||||
* The minimum possible value for packet size is 10. The maximum possible value of packet size
|
||||
* is 4096. If the response is too large to fit into one packet, it will be split and sent as
|
||||
* multiple packets.
|
||||
*
|
||||
* @param[id] The packet id field is a 32-bit little endian integer chosen by the client for each
|
||||
* request. It may be set to any positive integer. When the server responds to the request, the
|
||||
* response packet will have the same packet id as the original request (unless it is a failed
|
||||
* [PacketType.AUTH_RESPONSE] packet.) It need not be unique, but if a unique packet id is
|
||||
* assigned, it can be used to match incoming responses to their corresponding requests.
|
||||
*
|
||||
* @param[type] See [PacketType]
|
||||
*
|
||||
* @param[body] The packet body field is a null-terminated string encoded in ASCII (i.e. ASCIIZ).
|
||||
* Depending on the packet type, it may contain either the RCON password for the server, the command
|
||||
* to be executed, or the server's response to a request.
|
||||
*/
|
||||
data class Packet(
|
||||
val id: UInt,
|
||||
val type: PacketType,
|
||||
val bodyRaw: String,
|
||||
) {
|
||||
private val bodyTerminator: Byte = NULL_CHAR_BYTE
|
||||
|
||||
private val body: ByteArray = bodyRaw.toByteArray(Charsets.UTF_8)
|
||||
|
||||
// val bodyBytes = body.toByteArray(Charsets.UTF_8)
|
||||
/** The size of this packet (note that the size itself is excluded from the calculation.) */
|
||||
val size: Int = run {
|
||||
val idSize = 1 * Int.SIZE_BYTES
|
||||
val typeSize = 1 * Int.SIZE_BYTES
|
||||
val bodySize = body.size * Byte.SIZE_BYTES
|
||||
val bodyTerminatorSize = 1 * Byte.SIZE_BYTES
|
||||
val packetTerminatorSize = 1 * Byte.SIZE_BYTES
|
||||
val total = idSize + typeSize + bodySize + bodyTerminatorSize + packetTerminatorSize
|
||||
println(
|
||||
"packetSize: $total = $idSize + $typeSize + $bodySize + $bodyTerminatorSize + $packetTerminatorSize"
|
||||
)
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
val totalSize: Int = size + Int.SIZE_BYTES
|
||||
|
||||
init {
|
||||
println("totalSize: $totalSize")
|
||||
println("body: ${body.joinToString { it.toString() }}")
|
||||
}
|
||||
|
||||
// fun toByteArray(): ByteArray {
|
||||
// val array = buildList {
|
||||
// add(this@Packet.size.toByte())
|
||||
// add(id.toByte())
|
||||
// add(type.value.toByte())
|
||||
// addAll(body.toByteArray(Charsets.UTF_8).asIterable())
|
||||
// add(bodyTerminator)
|
||||
// add(RCON_PACKET_TERMINATOR)
|
||||
// }.toByteArray()
|
||||
//
|
||||
// println(array.joinToString { it.toInt().toChar().toString().padStart(3, ' ') })
|
||||
// println(array.joinToString { it.toInt().toString().padStart(3, ' ') })
|
||||
// println(array.joinToString { it.toUInt().toString().padStart(3, ' ') })
|
||||
// println(array.joinToString { it.toString().padStart(3, ' ') })
|
||||
//
|
||||
//// require(array.size == totalSize) { "${array.size} == $totalSize" }
|
||||
// return array
|
||||
// }
|
||||
|
||||
fun toByteBuffer2(): ByteBuffer {
|
||||
|
||||
return ByteBuffer
|
||||
.allocate(totalSize)
|
||||
.order(ByteOrder.LITTLE_ENDIAN)
|
||||
// size
|
||||
.putInt(size)
|
||||
// ID
|
||||
.putInt(id.toInt())
|
||||
// type
|
||||
.putInt(type.value)
|
||||
// body
|
||||
.put(body)
|
||||
// body terminator
|
||||
.put(bodyTerminator)
|
||||
// packet terminator
|
||||
.put(RCON_PACKET_TERMINATOR)
|
||||
// .asReadOnlyBuffer()
|
||||
|
||||
|
||||
}
|
||||
|
||||
// fun toByteBuffer(): ByteBuffer {
|
||||
//
|
||||
//
|
||||
// val bodyBA = buildList {
|
||||
// add(id.toByte())
|
||||
// add(type.value.toByte())
|
||||
// addAll(bodyBytes.asIterable())
|
||||
// add(bodyTerminator)
|
||||
// add(RCON_PACKET_TERMINATOR)
|
||||
// }.toByteArray()
|
||||
////
|
||||
////
|
||||
//// val body = ByteBuffer.allocate(totalSize + 15).apply {
|
||||
//// order(ByteOrder.LITTLE_ENDIAN)
|
||||
////// putInt(this@Packet.size)
|
||||
//// putInt(this@Packet.id.toInt())
|
||||
//// putInt(this@Packet.type.value)
|
||||
//// put(this@Packet.bodyBytes)
|
||||
//// put(this@Packet.terminator)
|
||||
//// }
|
||||
//
|
||||
// val bodySize = bodyBA.size //* Byte.SIZE_BYTES
|
||||
// println("bodySize: $bodySize")
|
||||
//
|
||||
// val bb = ByteBuffer.allocate(bodySize + Int.SIZE_BYTES).apply {
|
||||
// order(ByteOrder.LITTLE_ENDIAN)
|
||||
// putInt(bodySize)
|
||||
//// put(body.array())
|
||||
// put(bodyBA)
|
||||
// }
|
||||
//
|
||||
// require(
|
||||
// bb.array().size == totalSize) { "${bb.array().size} == $totalSize ($size + ${Int.SIZE_BYTES})" }
|
||||
//
|
||||
// return bb
|
||||
// }
|
||||
|
||||
companion object {
|
||||
/** [Char.MIN_VALUE] = null char */
|
||||
const val NULL_CHAR_BYTE = Char.MIN_VALUE.code.toByte()
|
||||
|
||||
/**
|
||||
* The end of a Source RCON Protocol packet is marked by an empty ASCIIZ string -
|
||||
* essentially just 8 bits of 0. In languages that do not null-terminate strings automatically,
|
||||
* you can simply write 16 bits of 0 immediately after the packet body (8 to terminate the
|
||||
* packet body and 8 for the empty string afterwards).
|
||||
*/
|
||||
const val RCON_PACKET_TERMINATOR = NULL_CHAR_BYTE
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package dev.adamko.ktrcon
|
||||
/**
|
||||
* The packet type field is a 32-bit little endian integer, which indicates the purpose of the
|
||||
* packet. Its value will always be either 0, 2, or 3, depending on which of the request/response
|
||||
* types the packet represents.
|
||||
*/
|
||||
enum class PacketType(
|
||||
val value: Int,
|
||||
val descriptor: String,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Typically, the first packet sent by the client will be a [AUTH_RESPONSE] packet, which is
|
||||
* used to authenticate the connection with the server.
|
||||
*
|
||||
* The value of the packet's fields are as follows:
|
||||
*
|
||||
* * **ID** - any positive integer, chosen by the client
|
||||
* (will be mirrored back in the server's response)
|
||||
* * **Type** - 3
|
||||
* * **Body** - the RCON password of the server (if this matches the server's *rcon_password*
|
||||
* cvar, the auth will succeed)
|
||||
*
|
||||
* If the rcon_password cvar is not set, or if it is set to empty string, all
|
||||
* [AUTH_RESPONSE] requests will be refused.
|
||||
*/
|
||||
AUTH_REQUEST(3, "SERVERDATA_AUTH"),
|
||||
AUTH_RESPONSE(2, "SERVERDATA_AUTH_RESPONSE"),
|
||||
EXEC_COMMAND(2, "SERVERDATA_EXECCOMMAND"),
|
||||
RESPONSE_VALUE(0, "SERVERDATA_RESPONSE_VALUE"),
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package dev.adamko.ktrcon
|
||||
|
||||
class Password constructor(password: String) {
|
||||
private val password: ByteArray = password.encodeToByteArray()
|
||||
private val hashcode: Int = password.hashCode()
|
||||
|
||||
operator fun invoke() = password.decodeToString()
|
||||
|
||||
fun access(accessor: (password: String) -> Unit) {
|
||||
accessor(password.decodeToString())
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = (other as? Password)?.hashcode == this.hashcode
|
||||
|
||||
override fun hashCode() = hashcode
|
||||
}
|
||||
25
modules/kt-rcon/src/main/kotlin/dev/adamko/ktrcon/main.kt
Normal file
25
modules/kt-rcon/src/main/kotlin/dev/adamko/ktrcon/main.kt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package dev.adamko.ktrcon
|
||||
|
||||
import kotlin.time.Duration
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun main(): Unit = runBlocking {
|
||||
KtRconClient3(
|
||||
"localhost",
|
||||
27715,
|
||||
Password("coh9oongoojahX4")
|
||||
).use { client ->
|
||||
|
||||
|
||||
// client.send(PacketType.AUTH_REQUEST, "passwrd")
|
||||
client.send(PacketType.AUTH_REQUEST, "coh9oongoojahX4")
|
||||
delay(Duration.seconds(10))
|
||||
|
||||
client.send(PacketType.EXEC_COMMAND, "/help")
|
||||
|
||||
|
||||
delay(Duration.seconds(10))
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue