diff --git a/pom.xml b/pom.xml index ded2704..0bbf5cf 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.rubeta LegacyPing - 2.2 + 1.2 jar LegacyPing diff --git a/src/main/java/net/rubeta/legacyping/CrutchBukkitMirrorThread.java b/src/main/java/net/rubeta/legacyping/CrutchBukkitMirrorThread.java deleted file mode 100644 index b422206..0000000 --- a/src/main/java/net/rubeta/legacyping/CrutchBukkitMirrorThread.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.rubeta.legacyping; - -import net.minecraft.network.packets.Packet; -import net.minecraft.network.packets.Packet255KickDisconnect; -import org.bukkit.craftbukkit.CraftServer; -import org.bukkit.craftbukkit.event.CraftEventFactory; -import org.bukkit.event.server.ServerListPingEvent; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.net.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class CrutchBukkitMirrorThread extends Thread { - - private final AtomicBoolean working = new AtomicBoolean(false); - private final Logger logger; - private ServerSocket serverSocket; - private final InetAddress address; - private final LegacyPing plugin; - - public CrutchBukkitMirrorThread(final LegacyPing plugin) { - this.plugin = plugin; - this.logger = Logger.getLogger("mirror:25565"); - - try { - this.address = InetAddress.getByName("0.0.0.0"); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - } - - @Override - public void run() { - this.working.set(true); - while (this.working.get()) { - try { - this.serverSocket = new ServerSocket(25565, 10, address); - this.serverSocket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L)); - this.serverSocket.setPerformancePreferences(0, 2, 1); - } catch (IOException e) { - this.logger.log(Level.WARNING, "Failed to start ServerSocket", e); - this.pause(); - continue; - } - - while (!this.serverSocket.isClosed()) { - if (!this.working.get()) { - break; - } - - InetAddress address = null; - try (final Socket socket = this.serverSocket.accept()) { - final DataInputStream inputStream = new DataInputStream(socket.getInputStream()); - final DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream()); - address = socket.getInetAddress(); - socket.setTrafficClass(SocketOptions.TCP_NODELAY & SocketOptions.IP_TOS); - inputStream.readInt(); // Packet length - Packet packet = Packet.readPacket(inputStream); - if (packet != null && packet.getPacketId() == 254) { - CraftServer server = (CraftServer) this.plugin.getServer(); - ServerListPingEvent pingEvent = CraftEventFactory.callServerListPingEvent( - server, address, server.getServer().motd, - server.getOnlinePlayers().length, server.getMaxPlayers() - ); - String s = pingEvent.getMotd() + "\u0000" + server.getOnlinePlayers().length + "\u0000" + pingEvent.getMaxPlayers(); - Packet255KickDisconnect response = new Packet255KickDisconnect(s); - outputStream.writeInt(s.length() + 1); - outputStream.writeByte(255); - response.writePacketData(outputStream); - } - } catch (SocketTimeoutException | EOFException ignore) { - } catch (Exception ex) { - this.logger.warning("An exception occurred while communicating with " + address); - this.logger.log(Level.WARNING, "Exception in " + this.getName(), ex); - } - } - } - } - - - public void shutdown() { - this.working.set(false); - try { - this.serverSocket.close(); - } catch (IOException ignore) { - } - } - - - void pause() { - this.pause(TimeUnit.SECONDS.toMillis(30L)); - } - - void pause(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException ignored) { - } - } -} diff --git a/src/main/java/net/rubeta/legacyping/LegacyPing.java b/src/main/java/net/rubeta/legacyping/LegacyPing.java index fc15d1d..1b3b756 100644 --- a/src/main/java/net/rubeta/legacyping/LegacyPing.java +++ b/src/main/java/net/rubeta/legacyping/LegacyPing.java @@ -1,8 +1,5 @@ package net.rubeta.legacyping; -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -13,54 +10,25 @@ import java.util.logging.Level; public final class LegacyPing extends JavaPlugin { private final List threads = new ArrayList<>(); - private CrutchBukkitMirrorThread mirror = null; @Override public void onEnable() { this.saveDefaultConfig(); - this.startThreads(); - } - - private void startThreads() { final FileConfiguration config = this.getConfig(); final String host = config.getString("host", "0.0.0.0"); final List ports = config.getIntegerList("port"); - final String version = config.getString("displayedVersion", "b1.7.3"); - final int protocol = config.getInt("displayedProtocol", 14); for (int port : ports) { try { - this.threads.add(new ServerThread(this, host, port, version, protocol)); + this.threads.add(new ServerThread(this.getServer(), host, port)); } catch (IOException e) { this.getLogger().log(Level.WARNING, "Failed to create ServerSocket", e); } } - if (config.getBoolean("enableMirrorPort", false)) { - this.mirror = new CrutchBukkitMirrorThread(this); - this.mirror.start(); - } this.threads.forEach(Thread::start); } @Override public void onDisable() { - if (this.mirror != null) { - this.mirror.shutdown(); - } this.threads.forEach(ServerThread::shutdown); } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!sender.isOp()) { - return true; - } - if (this.mirror != null) { - this.mirror.shutdown(); - } - this.threads.forEach(ServerThread::shutdown); - this.threads.clear(); - this.startThreads(); - sender.sendMessage(ChatColor.GOLD + "Restarted"); - return true; - } } diff --git a/src/main/java/net/rubeta/legacyping/ServerThread.java b/src/main/java/net/rubeta/legacyping/ServerThread.java index 8448a5f..d03ee9b 100644 --- a/src/main/java/net/rubeta/legacyping/ServerThread.java +++ b/src/main/java/net/rubeta/legacyping/ServerThread.java @@ -5,127 +5,67 @@ import org.bukkit.craftbukkit.CraftServer; import java.io.*; import java.net.*; -import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; public class ServerThread extends Thread { private static final String MODERN_RESPONSE = - "{\"version\": {\"name\": \"%version%\",\"protocol\": %protocol%}," + - "\"players\": {\"max\": %max%,\"online\": %now%}," + - "\"description\": {\"text\": \"%motd%\"}," + - "\"enforcesSecureChat\": false,\"previewsChat\": false}"; - private static final String DISCONNECT_REASON = "[\"\",{\"text\":\"This is b1.7.3 server. Visit \"}," + - "{\"text\":\"https://rubeta.net\",\"underlined\":true},{\"text\":\" for more information.\"}]"; - private static final String SLOWSTACK_ID = "slowstack.tv"; - private static final String MATSCAN_ID = "matscan"; + "{\"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; - private final String version; - private final int protocol; - private final InetAddress address; - private final int port; - private final Logger logger; - private ServerSocket serverSocket; - public ServerThread(final LegacyPing plugin, final String host, int port, String version, int protocol) throws IOException { - this.minecraftServer = plugin.getServer(); - this.logger = plugin.getLogger(); + public ServerThread(final Server server, final String host, int port) throws IOException { + this.minecraftServer = server; this.setName("LegacyPing thread listening " + host + ":" + port); - this.version = version; - this.protocol = protocol; - this.address = InetAddress.getByName(host); - this.port = 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); - this.logger = Logger.getLogger(host + ":" + port); - this.version = "dummy"; - this.protocol = 0; - this.address = InetAddress.getByName(host); - this.port = 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("", 25569).start(); + new ServerThread("", 25565).start(); } - @SuppressWarnings("unused") @Override public void run() { this.working.set(true); - while (this.working.get()) { + while (this.working.get() && !this.serverSocket.isClosed()) { try { - this.serverSocket = new ServerSocket(port, 10, address); - this.serverSocket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L)); - this.serverSocket.setPerformancePreferences(0, 2, 1); - } catch (IOException e) { - this.logger.log(Level.WARNING, "Failed to start ServerSocket", e); - this.pause(); - continue; - } - - while (!this.serverSocket.isClosed()) { - if (!this.working.get()) { - break; - } - InetAddress address = null; - try (final Socket socket = this.serverSocket.accept()) { - final DataInputStream inputStream = new DataInputStream(socket.getInputStream()); - final DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream()); - address = socket.getInetAddress(); - socket.setTrafficClass(SocketOptions.TCP_NODELAY & SocketOptions.IP_TOS); - - final int packetLength = inputStream.readByte() | inputStream.readByte() << 1; // idk why it doesn't a VarInt - final int protocolVersion = this.readVarInt(inputStream); - final String hostname = this.readUtf(inputStream); - final int port = inputStream.readShort(); - final int status = this.readVarInt(inputStream); - this.logger.info("Ping from " + socket.getRemoteSocketAddress() + " to " + hostname + ":" + port); - if (status != 2) { - if (hostname.equalsIgnoreCase(SLOWSTACK_ID)) { - this.responseForModernPing(outputStream, MODERN_RESPONSE.replace( - "%motd%", "%motd% \nHello, slowstack.tv!" - )); - } else if (hostname.equalsIgnoreCase(MATSCAN_ID)) { - this.responseForModernPing(outputStream, MODERN_RESPONSE - .replace("%motd%", "%motd% \nHello, matscan!") - .replace("%version%", "⚠ Error") - ); - } else { - this.responseForModernPing(outputStream, MODERN_RESPONSE); - } - } else { - this.responseForModernPing(outputStream, DISCONNECT_REASON); - socket.close(); - continue; - } - outputStream.flush(); - - this.pause(50L); - if (socket.isClosed() || !socket.isConnected() || inputStream.available() == 0) { - continue; - } - inputStream.readShort(); // legacy data - this.pause(50L); - if (socket.isClosed() || !socket.isConnected() || inputStream.available() == 0) { - continue; - } - this.writeVarInt(outputStream, this.readVarInt(inputStream)); // Packet length (9) - this.writeVarInt(outputStream, this.readVarInt(inputStream)); // Packet id (1) - outputStream.writeLong(inputStream.readLong()); // timestamp - } catch (SocketTimeoutException | EOFException ignore) { - } catch (Exception ex) { - if (this.working.get()) { - this.logger.warning("An exception occurred while communicating with " + address); - this.logger.log(Level.WARNING, "Exception in " + this.getName(), ex); - } + 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); } } } @@ -138,31 +78,44 @@ public class ServerThread extends Thread { } } - void pause() { - this.pause(TimeUnit.SECONDS.toMillis(30L)); - } - - void pause(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException ignored) { + 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()); } - private void responseForModernPing(final DataOutputStream outputStream, String response) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(baos); - final String responseJson = response - .replace("%version%", String.valueOf(this.version)) - .replace("%protocol%", String.valueOf(this.protocol)) - .replace("%max%", String.valueOf(this.getMaxPlayers())) + @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()); - this.writeVarInt(dos, 0); - this.writeUtf(dos, responseJson); - dos.close(); - this.writeVarInt(outputStream, baos.size()); - outputStream.write(baos.toByteArray()); + 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() { @@ -177,46 +130,14 @@ public class ServerThread extends Thread { return this.minecraftServer == null ? 37 : this.minecraftServer.getMaxPlayers(); } - public int readVarInt(DataInputStream in) { - int i = 0; - int j = 0; - try { - byte b0; - do { - b0 = in.readByte(); - i |= (b0 & 127) << j++ * 7; - if (j > 5) { - throw new RuntimeException("VarInt too big"); - } - } while ((b0 & 128) == 128); - } catch (Exception ignore) { - return 0; - } - return i; - } - private void writeVarInt(DataOutputStream out, int paramInt) throws IOException { - while ((paramInt & 0xffffff80) != 0) { - out.writeByte(paramInt & 0x7f | 0x80); + while (true) { + if ((paramInt & 0xFFFFFF80) == 0) { + out.writeByte(paramInt); + return; + } + out.writeByte(paramInt & 0x7F | 0x80); paramInt >>>= 7; } - out.writeByte(paramInt); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private String readUtf(DataInputStream in) throws IOException { - int length = this.readVarInt(in); - byte[] data = new byte[length]; - in.read(data); - return new String(data, StandardCharsets.UTF_8); - } - - private void writeUtf(DataOutputStream out, String string) throws IOException { - byte[] data = string.getBytes(StandardCharsets.UTF_8); - if (data.length > 32767) { - throw new RuntimeException("String too big (was " + data.length + " bytes encoded, max 32767"); - } - this.writeVarInt(out, data.length); - out.write(data); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f06ef01..803f05f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,5 +2,3 @@ host: '0.0.0.0' port: - 25566 - 25567 -displayedVersion: 'b1.7.3' -displayedProtocol: 17 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 20fa2c4..284ec33 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,10 +1,3 @@ name: LegacyPing version: '${project.version}' main: net.rubeta.legacyping.LegacyPing -commands: - legacyping: - permission: legacyping.command.legacyping - -permissions: - legacyping.command.legacyping: - default: op