diff --git a/pom.xml b/pom.xml
index d294de5..4f0fb9a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,6 +5,13 @@
ServerList
1.0-SNAPSHOT
jar
+
+
+ commons-cli
+ commons-cli
+ 1.5.0
+
+
diff --git a/src/main/java/com/diamante/serverlist/ClientEmulator.java b/src/main/java/com/diamante/serverlist/ClientEmulator.java
new file mode 100644
index 0000000..a745320
--- /dev/null
+++ b/src/main/java/com/diamante/serverlist/ClientEmulator.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 Diamante
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.diamante.serverlist;
+
+import java.lang.management.ManagementFactory;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.nio.channels.IllegalBlockingModeException;
+
+import java.util.Set;
+
+/**
+ * The purpose of this module is to ping the servers on the server list.
+ *
+ * @author Diamante
+ */
+public class ClientEmulator implements Runnable {
+
+ public static final int SERVER_QUERY = 1347374924;
+
+ public static final int SERVER_INFO_SIZE = 2129;
+
+ private final Set servers;
+
+ private static final int SOCKET_TIMEOUT = 4000;
+ private DatagramSocket socket;
+
+ public ClientEmulator(Set servers) {
+ try {
+ socket = new DatagramSocket();
+ socket.setSoTimeout(SOCKET_TIMEOUT);
+ }
+ catch (SocketException ex) {
+ System.err.println("ClientEmulator: SocketException while creating new DatagramSocket");
+ System.exit(-1);
+ }
+
+ this.servers = servers;
+ }
+
+ public ClientEmulator() {
+ try {
+ socket = new DatagramSocket();
+ socket.setSoTimeout(SOCKET_TIMEOUT);
+ }
+ catch (SocketException ex) {
+ System.err.println("ClientEmulator: SocketException while creating new DatagramSocket");
+ System.exit(-1);
+ }
+
+ this.servers = null;
+ }
+
+ public void pingSingleServer(String ip) {
+ var to = Utils.stringToServer(ip);
+ if (to != null) {
+ handleServer(to);
+ }
+ }
+
+ private void sendDatagramPacket(DatagramPacket packet) {
+ try {
+ socket.send(packet);
+ }
+ catch (IOException | IllegalArgumentException | IllegalBlockingModeException ex) {
+ // Socket will timeout
+ System.err.println("sendDatagramPacket: exception while sending a packet");
+ }
+ }
+
+ private DatagramPacket receiveDatagramPacket() {
+ var buffer = new byte[SERVER_INFO_SIZE];
+ var packet = new DatagramPacket(buffer, buffer.length);
+
+ try {
+ socket.receive(packet);
+ System.out.println(String.format("receiveDatagramPacket: Server %s:%d returned a packet", packet.getAddress().toString(), packet.getPort()));
+ return packet;
+ }
+ catch (SocketException ex) {
+ System.err.println("receiveDatagramPacket: SocketException");
+ return null;
+ }
+ catch (IllegalBlockingModeException ex) {
+ System.err.println("receiveDatagramPacket: IllegalBlockingModeException");
+ return null;
+ }
+ catch (SocketTimeoutException ex) {
+ System.err.println("receiveDatagramPacket: SocketTimeoutException");
+ return null;
+ }
+ catch (IOException ex) {
+ System.err.println("receiveDatagramPacket: IOException");
+ return null;
+ }
+ }
+
+ private byte[] generateClientPing() {
+ // Use this as tick
+ var jvmUpTime = (Long) ManagementFactory.getRuntimeMXBean().getUptime();
+
+ var tickLE = Utils.longSwap(jvmUpTime.intValue());
+ var magicLE = Utils.longSwap(SERVER_QUERY);
+
+ var data = new byte[8];
+
+ System.arraycopy(magicLE, 0, data, 0, 4);
+ System.arraycopy(tickLE, 0, data, 4, 4);
+
+ return data;
+ }
+
+ private void sendPingToServer(Server server) {
+ var data = generateClientPing();
+ // We use the net_port like a client would.
+ var packet = new DatagramPacket(data, data.length, server.getAddress(), server.getNetPort());
+ sendDatagramPacket(packet);
+ }
+
+ private void handleServer(Server server) {
+ sendPingToServer(server);
+ var response = receiveDatagramPacket();
+ if (response == null) {
+ return;
+ }
+
+ var rawData = response.getData();
+ InfoDumper.dumpServerResponse(rawData);
+ }
+
+ @Override
+ public void run() {
+ synchronized (servers) {
+ var it = servers.iterator();
+ while (it.hasNext()) {
+ var server = it.next();
+ handleServer(server);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/diamante/serverlist/InfoDumper.java b/src/main/java/com/diamante/serverlist/InfoDumper.java
new file mode 100644
index 0000000..4d9a025
--- /dev/null
+++ b/src/main/java/com/diamante/serverlist/InfoDumper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 Diamante
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.diamante.serverlist;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ *
+ * @author Diamante
+ */
+public class InfoDumper {
+
+ public static void dumpServerResponse(byte[] data) {
+ assert data.length == ClientEmulator.SERVER_INFO_SIZE;
+
+ var magicLE = new byte[4];
+
+ var playersLE = new byte[4];
+ var maxPlayersLE = new byte[4];
+
+ var rawDataLE = new byte[2048];
+
+ System.arraycopy(data, 0, magicLE, 0, 4);
+ System.arraycopy(data, 8, playersLE, 0, 4);
+ System.arraycopy(data, 12, maxPlayersLE, 0, 4);
+
+ System.arraycopy(data, 81, rawDataLE, 0, 2048);
+
+ var playersBE = Utils.longSwap(playersLE);
+ var maxPlayersBE = Utils.longSwap(maxPlayersLE);
+
+ System.out.println(String.format("dumpServerResponse: Players %d:%d", playersBE, maxPlayersBE));
+
+ String infoString = new String(rawDataLE, StandardCharsets.UTF_8);
+ System.out.println(infoString);
+ }
+}
diff --git a/src/main/java/com/diamante/serverlist/Main.java b/src/main/java/com/diamante/serverlist/Main.java
index f9116c1..2a624c2 100644
--- a/src/main/java/com/diamante/serverlist/Main.java
+++ b/src/main/java/com/diamante/serverlist/Main.java
@@ -18,24 +18,67 @@ package com.diamante.serverlist;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+import org.apache.commons.cli.ParseException;
+
/**
*
* @author Diamante
*/
public class Main {
+ private enum Mode {
+ Emulator, Master, Bad;
+ }
+
+ private Mode mode;
+
public static final AtomicBoolean running = new AtomicBoolean(true);
- private final MasterServer server;
+ private MasterServer server;
public Main() {
- server = new MasterServer();
+ mode = Mode.Bad;
+ }
+
+ private Mode getMode() {
+ return mode;
+ }
+
+ private void setMode(Mode mode) {
+ this.mode = mode;
}
public MasterServer getServer() {
return server;
}
+ private void createMasterServer() {
+ server = new MasterServer();
+ }
+
+ private Options createOptions() {
+ var options = new Options();
+
+ var master = new Option("master", "master server mode");
+ var emulator = new Option("emulator", "client emulator mode");
+
+ var ping = Option.builder("ping")
+ .argName("IP:Port")
+ .hasArg()
+ .desc("Server to ping")
+ .build();
+
+ options.addOption(master);
+ options.addOption(emulator);
+ options.addOption(ping);
+
+ return options;
+ }
+
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
@@ -46,11 +89,37 @@ public class Main {
});
var main = new Main();
+ var options = main.createOptions();
+ var ip = new String();
- while (running.get() && main.getServer().isValid()) {
- main.getServer().await();
+ var parser = new DefaultParser();
+ try {
+ var line = parser.parse(options, args);
+ if (line.hasOption("master")) {
+ main.setMode(Mode.Master);
+ } else if (line.hasOption("emulator")) {
+ main.setMode(Mode.Emulator);
+ }
+
+ if (line.hasOption("ping")) {
+ ip = line.getOptionValue("ping");
+ }
+ }
+ catch (ParseException exp) {
+ System.err.println("Parsing failed. Reason: " + exp.getMessage());
}
- main.getServer().stop();
+ if (main.getMode() == Mode.Master) {
+ main.createMasterServer();
+ while (running.get() && main.getServer().isValid()) {
+ main.getServer().await();
+ }
+ main.getServer().stop();
+ } else if (main.getMode() == Mode.Emulator) {
+ var emulator = new ClientEmulator();
+ emulator.pingSingleServer(ip);
+ }
+
+ System.out.println("Normal shutdown");
}
}
diff --git a/src/main/java/com/diamante/serverlist/MasterServer.java b/src/main/java/com/diamante/serverlist/MasterServer.java
index e44d7bc..48a03e3 100644
--- a/src/main/java/com/diamante/serverlist/MasterServer.java
+++ b/src/main/java/com/diamante/serverlist/MasterServer.java
@@ -101,6 +101,8 @@ public class MasterServer {
} else {
System.out.println("handlePacket: magic is not recognized");
}
+
+ serverList.dumpOnlineServers();
}
public void await() {
diff --git a/src/main/java/com/diamante/serverlist/Server.java b/src/main/java/com/diamante/serverlist/Server.java
index bf50c1c..1bb8819 100644
--- a/src/main/java/com/diamante/serverlist/Server.java
+++ b/src/main/java/com/diamante/serverlist/Server.java
@@ -42,11 +42,18 @@ public class Server {
this.time = System.currentTimeMillis() / 1000L;
}
+ public Server(InetAddress address, Short netPort) {
+ this.address = address;
+ this.netPort = netPort;
+
+ this.time = System.currentTimeMillis() / 1000L;
+ }
+
public InetAddress getAddress() {
return address;
}
-
- public short getNetPort(){
+
+ public short getNetPort() {
return netPort;
}
diff --git a/src/main/java/com/diamante/serverlist/ServerList.java b/src/main/java/com/diamante/serverlist/ServerList.java
index 30a8426..e104138 100644
--- a/src/main/java/com/diamante/serverlist/ServerList.java
+++ b/src/main/java/com/diamante/serverlist/ServerList.java
@@ -79,7 +79,7 @@ public class ServerList {
/**
* The first 4 bytes will contain the numbers of servers we are going to
* send in LE Then we have 4 bytes for the IP address in LE Finally 2 bytes
- * for the net_port in LE Repeat for each server
+ * for the net_port in LE. Repeat for each server
*
* @param version the version of the client
* @return the raw bytes to send to the client
@@ -117,7 +117,7 @@ public class ServerList {
builder.write(portLE);
}
catch (IOException ex) {
- System.err.println("createResponse: IOException while writing server data)");
+ System.err.println("createResponse: IOException while writing server data");
}
}
}
@@ -128,6 +128,23 @@ public class ServerList {
byteBuffer.clear();
byteBuffer.put(builder.toByteArray());
byteBuffer.flip();
+
+ try {
+ builder.close();
+ }
+ catch (IOException ex) {
+ System.err.println("createResponse: IOException in builder.close()");
+ }
+
return byteBuffer.array();
}
+
+ public void dumpOnlineServers() {
+ if (!Main.running.get()) {
+ return;
+ }
+
+ var thread = new Thread(new ClientEmulator(serverList));
+ thread.start();
+ }
}
diff --git a/src/main/java/com/diamante/serverlist/Utils.java b/src/main/java/com/diamante/serverlist/Utils.java
index beb6e7b..9a4d85c 100644
--- a/src/main/java/com/diamante/serverlist/Utils.java
+++ b/src/main/java/com/diamante/serverlist/Utils.java
@@ -16,6 +16,10 @@
*/
package com.diamante.serverlist;
+import java.net.InetAddress;
+
+import java.net.UnknownHostException;
+
import java.nio.BufferOverflowException;
import java.nio.ReadOnlyBufferException;
@@ -48,7 +52,7 @@ public class Utils {
}
/**
- * Flips the array of in around
+ * Flips the array around
*
* @param in array of in. Length must be multiple of 4
*/
@@ -113,7 +117,7 @@ public class Utils {
return buffer.getShort(0);
}
catch (BufferOverflowException | ReadOnlyBufferException ex) {
- System.err.println("longSwap: BufferOverflowException or ReadOnlyBufferException");
+ System.err.println("shortSwap: BufferOverflowException or ReadOnlyBufferException");
return -1;
}
}
@@ -129,4 +133,19 @@ public class Utils {
buffer.putShort(in);
return buffer.array();
}
+
+ public static Server stringToServer(String in) {
+ try {
+ var parts = in.split(":");
+
+ if (parts.length < 2) {
+ return null;
+ }
+
+ return new Server(InetAddress.getByName(parts[0]), Short.parseShort(parts[1]));
+ }
+ catch (UnknownHostException ex) {
+ return null;
+ }
+ }
}