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