package net.rubeta.legacyping; import org.bukkit.Server; import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class ServerThread extends Thread { // Queries start private static final byte[] RUBETA_MODDED_CLIENT = new byte[]{0, 0, 0, 1, (byte) 254}; private static final byte[] LEGACY_PING_B1_8_TO_R1_3 = new byte[]{(byte) 254}; private static final byte[] PING_R1_4_TO_R1_5 = new byte[]{(byte) 254, 1}; // Queries end 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; final InetAddress address = InetAddress.getByName(host); this.serverSocket = new ServerSocket(port, 10, address); this.serverSocket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L)); } @Override public void run() { this.working.set(true); while (this.working.get() || !this.serverSocket.isClosed()) { try { final Socket newConnection = this.serverSocket.accept(); final InputStream inputStream = newConnection.getInputStream(); final ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buffer = new byte[16]; for (int length; (length = inputStream.read(buffer)) != -1; ) { result.write(buffer, 0, length); } final byte[] query = result.toByteArray(); final OutputStream outputStream = newConnection.getOutputStream(); if (Arrays.equals(RUBETA_MODDED_CLIENT, query)) { this.responseForRuBetaModdedClient(outputStream); } else if (Arrays.equals(LEGACY_PING_B1_8_TO_R1_3, query)) { this.responseForLegacyPing(outputStream); } else if (Arrays.equals(PING_R1_4_TO_R1_5, query)) { this.responseForR14Ping(outputStream); } newConnection.close(); } catch (IOException ignore) { } } } 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.minecraftServer.getMotd() + '\u0000' + this.minecraftServer.getOnlinePlayers().size() + '\u0000' + this.minecraftServer.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()); } private void responseForLegacyPing(final OutputStream outputStream) throws IOException { final DataOutputStream clientOutput = new DataOutputStream(outputStream); clientOutput.write(255); // Packet 0xFF (PacketDisconnect) final String response = this.minecraftServer.getMotd() + '\u0000' + this.minecraftServer.getOnlinePlayers().size() + '\u0000' + this.minecraftServer.getMaxPlayers(); clientOutput.writeShort(response.length()); for (char c : response.toCharArray()) { clientOutput.writeChar(c); } // Packet data end } private void responseForR14Ping(final OutputStream outputStream) throws IOException { final DataOutputStream clientOutput = new DataOutputStream(outputStream); clientOutput.write(255); // Packet 0xFF (PacketDisconnect) final String response = 'ยง' + 1 + '\u0000' + "b1.7.3m" + '\u0000' + 17 // Protocol version + '\u0000' + this.minecraftServer.getMotd() + '\u0000' + this.minecraftServer.getOnlinePlayers().size() + '\u0000' + this.minecraftServer.getMaxPlayers(); clientOutput.writeShort(response.length()); for (char c : response.toCharArray()) { clientOutput.writeChar(c); } // Packet data end } }