From 6ea90b46ffdc5dc4940411ded7aa042495a56a6b Mon Sep 17 00:00:00 2001 From: 502647092 Date: Thu, 28 Sep 2017 20:54:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E7=9A=84MC=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 502647092 --- .../java/pw/yumc/YumCore/mc/ServerInfo.java | 202 ++++++++++-------- .../pw/yumc/YumCore/mc/ServerInfoTest.java | 20 ++ 2 files changed, 128 insertions(+), 94 deletions(-) create mode 100644 src/test/java/pw/yumc/YumCore/mc/ServerInfoTest.java diff --git a/src/main/java/pw/yumc/YumCore/mc/ServerInfo.java b/src/main/java/pw/yumc/YumCore/mc/ServerInfo.java index a9ace4e..1c3a3e5 100644 --- a/src/main/java/pw/yumc/YumCore/mc/ServerInfo.java +++ b/src/main/java/pw/yumc/YumCore/mc/ServerInfo.java @@ -1,24 +1,53 @@ package pw.yumc.YumCore.mc; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; -import java.nio.charset.Charset; +import java.util.regex.Pattern; +import lombok.Data; import pw.yumc.YumCore.annotation.NotProguard; /** * Minecraft服务器数据获取类 - * + * * @author 喵♂呜 * @since 2017/1/26 0026 */ +@Data @NotProguard public class ServerInfo { + private static byte PACKET_HANDSHAKE = 0x00, PACKET_STATUSREQUEST = 0x00, PACKET_PING = 0x01; + private static int PROTOCOL_VERSION = 4; + private static int STATUS_HANDSHAKE = 1; + private static Pattern pattern = Pattern.compile(".*\"description\":\"(.*)\".*"); + /** + * { + * "version": {"name": "BungeeCord 1.8.x, 1.9.x, 1.10.x, 1.11.x", "protocol": 316}, + * "players": {"max": 922, "online": 921}, + * "description": { + * "extra": [ + * {"color": "white", "text": " "}, + * {"color": "aqua", "bold": true, "text": "梦世界 "}, + * {"color": "red","bold": true, "obfuscated": true, "text": "|"}, + * {"color": "white", "text": " "}, + * {"color": "white", "bold": true, "text": "i5mc.com\n"}, + * {"color": "white", "text": " " }, + * {"color": "yellow", "bold": true, "text": "★"}, + * {"color": "red", "bold": true, "text": "1.8-1.11"}, + * {"color": "yellow", "bold": true, "text": "★ ★"}, + * {"color": "yellow", "text": "黄金周末"}, + * {"color": "gray", "text": "-"}, + * {"color": "green", "text": "空岛战争"}, + * {"color": "red", "bold": true, "text": "双倍硬币"}, + * {"color": "green", "text": "奖励"}, + * {"color": "yellow", "bold": true, "text": "★"} + * ],"text": "" } + * } + */ private String address = "localhost"; private int port = 25565; private int timeout = 1500; @@ -31,9 +60,9 @@ public class ServerInfo { /** * Minecraft服务器数据获取类 - * + * * @param address - * 服务器地址 默认端口25565 + * 服务器地址 默认端口25565 */ public ServerInfo(String address) { this(address, 25565); @@ -41,11 +70,11 @@ public class ServerInfo { /** * Minecraft服务器数据获取类 - * + * * @param address - * 服务器地址 + * 服务器地址 * @param port - * 服务器端口 + * 服务器端口 */ public ServerInfo(String address, int port) { this.address = address; @@ -54,68 +83,61 @@ public class ServerInfo { /** * 获取服务器数据 - * + * * @return 是否获取成功 */ public boolean fetchData() { try (Socket socket = new Socket()) { - OutputStream outputStream; - DataOutputStream dataOutputStream; - InputStream inputStream; - InputStreamReader inputStreamReader; + socket.connect(new InetSocketAddress(getAddress(), getPort()), getTimeout()); - socket.setSoTimeout(this.timeout); + final DataInputStream in = new DataInputStream(socket.getInputStream()); + final DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - socket.connect(new InetSocketAddress(this.getAddress(), this.getPort()), this.getTimeout()); + //> Handshake + ByteArrayOutputStream handshake_bytes = new ByteArrayOutputStream(); + DataOutputStream handshake = new DataOutputStream(handshake_bytes); - outputStream = socket.getOutputStream(); - dataOutputStream = new DataOutputStream(outputStream); + handshake.writeByte(PACKET_HANDSHAKE); + writeVarInt(handshake, PROTOCOL_VERSION); + writeVarInt(handshake, getAddress().length()); + handshake.writeBytes(getAddress()); + handshake.writeShort(getPort()); + writeVarInt(handshake, STATUS_HANDSHAKE); - inputStream = socket.getInputStream(); - inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-16BE")); + writeVarInt(out, handshake_bytes.size()); + out.write(handshake_bytes.toByteArray()); - dataOutputStream.write(new byte[] { (byte) 0xFE, (byte) 0x01 }); + //> Status request - int packetId = inputStream.read(); + out.writeByte(0x01); // Size of packet + out.writeByte(PACKET_STATUSREQUEST); - if (packetId == -1) { throw new IOException("Premature end of stream."); } + //< Status response - if (packetId != 0xFF) { throw new IOException("Invalid packet ID (" + packetId + ")."); } + readVarInt(in); // Size + int id = readVarInt(in); - int length = inputStreamReader.read(); + int length = readVarInt(in); - if (length == -1) { throw new IOException("Premature end of stream."); } + byte[] data = new byte[length]; + in.readFully(data); + String json = new String(data, "UTF-8"); + System.out.println(json); + // //> Ping + // + // out.writeByte(0x09); // Size of packet + // out.writeByte(PACKET_PING); + // out.writeLong(System.currentTimeMillis()); + // + // //< Ping + // readVarInt(in); // Size + // id = readVarInt(in); - if (length == 0) { throw new IOException("Invalid string length."); } - - char[] chars = new char[length]; - - if (inputStreamReader.read(chars, 0, length) != length) { throw new IOException("Premature end of stream."); } - - String string = new String(chars); - - if (string.startsWith("§")) { - String[] data = string.split("\0"); - this.pingVersion = Integer.parseInt(data[0].substring(1)); - this.protocolVersion = Integer.parseInt(data[1]); - this.gameVersion = data[2]; - this.motd = data[3]; - this.playersOnline = Integer.parseInt(data[4]); - this.maxPlayers = Integer.parseInt(data[5]); - } else { - String[] data = string.split("§"); - this.motd = data[0]; - this.playersOnline = Integer.parseInt(data[1]); - this.maxPlayers = Integer.parseInt(data[2]); - } - - dataOutputStream.close(); - outputStream.close(); - - inputStreamReader.close(); - inputStream.close(); - - socket.close(); + // Close + handshake.close(); + handshake_bytes.close(); + out.close(); + in.close(); } catch (IOException exception) { gameVersion = "获取失败!"; motd = "获取失败!"; @@ -124,44 +146,36 @@ public class ServerInfo { return true; } - public String getAddress() { - return this.address; + /** + * @author thinkofdeath + * See: https://gist.github.com/thinkofdeath/e975ddee04e9c87faf22 + */ + public int readVarInt(DataInputStream in) throws IOException { + int i = 0; + int j = 0; + while (true) { + int k = in.readByte(); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) + throw new RuntimeException("VarInt too big"); + if ((k & 0x80) != 128) + break; + } + return i; } - public int getPort() { - return this.port; + /** + * @author thinkofdeath + * See: https://gist.github.com/thinkofdeath/e975ddee04e9c87faf22 + */ + public void writeVarInt(DataOutputStream out, int paramInt) throws IOException { + while (true) { + if ((paramInt & 0xFFFFFF80) == 0) { + out.writeByte(paramInt); + return; + } + out.writeByte(paramInt & 0x7F | 0x80); + paramInt >>>= 7; + } } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public int getTimeout() { - return this.timeout; - } - - public int getPingVersion() { - return this.pingVersion; - } - - public int getProtocolVersion() { - return this.protocolVersion; - } - - public String getGameVersion() { - return this.gameVersion; - } - - public String getMotd() { - return this.motd; - } - - public int getPlayersOnline() { - return this.playersOnline; - } - - public int getMaxPlayers() { - return this.maxPlayers; - } - } diff --git a/src/test/java/pw/yumc/YumCore/mc/ServerInfoTest.java b/src/test/java/pw/yumc/YumCore/mc/ServerInfoTest.java new file mode 100644 index 0000000..dec3618 --- /dev/null +++ b/src/test/java/pw/yumc/YumCore/mc/ServerInfoTest.java @@ -0,0 +1,20 @@ +package pw.yumc.YumCore.mc; + + +import org.junit.Test; + +import lombok.val; + +/** + * Created with IntelliJ IDEA + * + * @author 喵♂呜 + * Created on 2017/9/28 18:50. + */ +public class ServerInfoTest { + @Test + public void test() { + val info = new ServerInfo("play.i5mc.com"); + info.fetchData(); + } +} \ No newline at end of file