/* * This file is a part of the SteamWar software. * * Copyright (C) 2024 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package de.steamwar.util import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import java.io.* import java.net.InetSocketAddress import java.net.Socket /** * * @author zh32 */ private fun readVarInt(`in`: DataInputStream): Int { var i = 0 var j = 0 while (true) { val k = `in`.readByte().toInt() i = i or (k and 0x7F shl j++ * 7) if (j > 5) throw RuntimeException("VarInt too big") if (k and 0x80 != 128) break } return i } private fun writeVarInt(out: DataOutputStream, paramInt: Int) { var paramInts = paramInt while (true) { if (paramInts and -0x80 == 0) { out.writeByte(paramInts) return } out.writeByte(paramInts and 0x7F or 0x80) paramInts = paramInts ushr 7 } } private val JSON = Json { ignoreUnknownKeys = true } fun fetchData(address: InetSocketAddress, timeout: Int = 7000): StatusResponse { val socket = Socket() socket.setSoTimeout(timeout) socket.connect(address, timeout) val outputStream = socket.getOutputStream() val dataOutputStream = DataOutputStream(outputStream) val inputStream = socket.getInputStream() val inputStreamReader = InputStreamReader(inputStream) val b = ByteArrayOutputStream() val handshake = DataOutputStream(b) handshake.writeByte(0x00) //packet id for handshake writeVarInt(handshake, 4) //protocol version writeVarInt(handshake, address.hostString.length) //host length handshake.writeBytes(address.hostString) //host string handshake.writeShort(address.port) //port writeVarInt(handshake, 1) //state (1 for handshake) writeVarInt(dataOutputStream, b.size()) //prepend size dataOutputStream.write(b.toByteArray()) //write handshake packet dataOutputStream.writeByte(0x01) //size is only 1 dataOutputStream.writeByte(0x00) //packet id for ping val dataInputStream = DataInputStream(inputStream) readVarInt(dataInputStream) //size of packet var id = readVarInt(dataInputStream) //packet id if (id == -1) { throw IOException("Premature end of stream.") } if (id != 0x00) { //we want a status response throw IOException("Invalid packetID") } val length = readVarInt(dataInputStream) //length of json string if (length == -1) { throw IOException("Premature end of stream.") } if (length == 0) { throw IOException("Invalid string length.") } val `in` = ByteArray(length) dataInputStream.readFully(`in`) //read json string val json = String(`in`) val now = System.currentTimeMillis() dataOutputStream.writeByte(0x09) //size of packet dataOutputStream.writeByte(0x01) //0x01 for ping dataOutputStream.writeLong(now) //time!? readVarInt(dataInputStream) id = readVarInt(dataInputStream) if (id == -1) { throw IOException("Premature end of stream.") } if (id != 0x01) { throw IOException("Invalid packetID") } val pingtime = dataInputStream.readLong() //read response val response: StatusResponse = JSON.decodeFromString(json) response.time = (now - pingtime).toInt() dataOutputStream.close() outputStream.close() inputStreamReader.close() inputStream.close() socket.close() return response } @Serializable data class StatusResponse(val description: JsonElement, val players: Players, val version: Version, val favicon: String) { @Transient var time = 0 } @Serializable data class Players(val max: Int, val online: Int, val sample: List = emptyList()) @Serializable data class Player(val name: String, val id: String) @Serializable data class Version(val name: String, val protocol: Int)