package net.rubeta.legacyping; import org.bukkit.Server; import org.bukkit.craftbukkit.CraftServer; import java.io.*; import java.net.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class ServerThread extends Thread { private static final String MODERN_RESPONSE = "{\"version\": {\"name\": \"b1.7.3\",\"protocol\": -17}," + "\"players\": {\"max\": %max%,\"online\": %now%,\"sample\": []}," + "\"description\": {\"text\": \"%motd%\"},\"enforcesSecureChat\": false}"; private final ServerSocket serverSocket; private final AtomicBoolean working = new AtomicBoolean(false); private final Server minecraftServer; public ServerThread(final Server server, final String host, int port) throws IOException { this.minecraftServer = server; this.setName("LegacyPing thread listening " + host + ":" + port); final InetAddress address = InetAddress.getByName(host); this.serverSocket = new ServerSocket(port, 10, address); this.serverSocket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L)); this.serverSocket.setPerformancePreferences(0, 2, 1); } public ServerThread(final String host, int port) throws IOException { this.minecraftServer = null; this.setName("LegacyPing thread listening " + host + ":" + port); final InetAddress address = InetAddress.getByName(host); this.serverSocket = new ServerSocket(port, 10, address); this.serverSocket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L)); this.serverSocket.setPerformancePreferences(0, 2, 1); } public static void main(String[] args) throws IOException { new ServerThread("", 25565).start(); } @Override public void run() { this.working.set(true); while (this.working.get() && !this.serverSocket.isClosed()) { try { final Socket socket = this.serverSocket.accept(); socket.setTrafficClass(SocketOptions.TCP_NODELAY & SocketOptions.IP_TOS); System.out.println("[LegacyPing] Got new ping from " + socket.getRemoteSocketAddress()); final InputStream inputStream = socket.getInputStream(); final OutputStream outputStream = socket.getOutputStream(); final int firstByte = inputStream.read(); switch (firstByte) { case 0: this.responseForRuBetaModdedClient(outputStream); break; case 254: this.responseForLegacyPing(outputStream); break; default: this.responseForModernPing(outputStream); } outputStream.flush(); socket.close(); } catch (SocketTimeoutException ignore) { } catch (IOException ex) { System.out.println("Exception caught: " + ex); } } } public void shutdown() { this.working.set(false); try { this.serverSocket.close(); } catch (IOException ignore) { } } private void responseForRuBetaModdedClient(final OutputStream outputStream) throws IOException { final ByteArrayOutputStream result = new ByteArrayOutputStream(); final DataOutputStream dos = new DataOutputStream(result); dos.write(255); // Packet 0xFF (PacketDisconnect) final String response = this.getMotd() + '\u0000' + this.getOnlinePlayers() + '\u0000' + this.getMaxPlayers(); dos.writeShort(response.length()); for (char c : response.toCharArray()) { dos.writeChar(c); } // Packet data end final DataOutputStream clientOutput = new DataOutputStream(outputStream); clientOutput.writeInt(result.size()); clientOutput.write(result.toByteArray()); } @SuppressWarnings("UnnecessaryUnicodeEscape") private void responseForLegacyPing(final OutputStream outputStream) throws IOException { final DataOutputStream clientOutput = new DataOutputStream(outputStream); clientOutput.write(255); final String response = this.getMotd() + '\u00a7' + this.getOnlinePlayers() + '\u00a7' + this.getMaxPlayers(); clientOutput.writeShort(response.length()); for (char c : response.toCharArray()) { clientOutput.writeChar(c); } // Packet data end } private void responseForModernPing(final OutputStream outputStream) throws IOException { final String responseJson = MODERN_RESPONSE.replace("%max%", String.valueOf(this.getMaxPlayers())) .replace("%now%", String.valueOf(this.getOnlinePlayers())) .replace("%motd%", this.getMotd()); byte[] bytes = responseJson.getBytes(); final DataOutputStream clientOutput = new DataOutputStream(outputStream); this.writeVarInt(clientOutput, bytes.length + 3); this.writeVarInt(clientOutput, 0); this.writeVarInt(clientOutput, bytes.length); clientOutput.write(bytes); // Packet data end } private String getMotd() { return this.minecraftServer == null ? "This is an example of the Message of the Day" : ((CraftServer) this.minecraftServer).getServer().motd; } private int getOnlinePlayers() { return this.minecraftServer == null ? 13 : this.minecraftServer.getOnlinePlayers().length; } private int getMaxPlayers() { return this.minecraftServer == null ? 37 : this.minecraftServer.getMaxPlayers(); } private 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; } } }