diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..6723403
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,26 @@
+name: Maven CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@main
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@main
+ with:
+ java-version: '21' # Change this to the required JDK version
+ distribution: 'oracle' # Use Eclipse Temurin distribution
+ cache: maven
+
+ - name: Build with Maven
+ run: mvn clean install
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a44c3b2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# ServerList: A Central Server & Utilities for an abandoned MW3 Client
+
+This project implements a 'central' server for the now-defunct MW3 modded client known as Tekno.
+
+The project was initially created in 2021 as a way to explore socket management in Java, a topic I studied at university. Two years ago, I refactored the code to simplify it, and more recently, I added new features such as logging and health monitoring for the central server.
+
+Interestingly, the original 'master' server for Tekno was written in just 20 lines of Python code. This highlights the complexity of working with Java. However, this project aims to go beyond the basics by introducing additional features, such as a self-cleaning thread to automatically remove servers from the list if they contained "crasher" strings. While this feature was part of the 2021 version, it is not included in this newer version due to time constraints. Additionally, this version lacks unit testing.
+
+## Features
+
+- **Central Server**: Manages and lists servers for the Tekno MW3 client.
+- **Server Pinger**: Fetches and dumps information from various servers currently listed on the central server.
+- **Central Server Pinger**: Retrieves and displays basic information about the central server.
diff --git a/pom.xml b/pom.xml
index 7d68023..69489bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
commons-cli
commons-cli
- 1.5.0
+ 1.9.0
com.googlecode.json-simple
diff --git a/src/main/java/com/diamante/serverlist/InfoDumper.java b/src/main/java/com/diamante/serverlist/InfoDumper.java
index 87ad9ed..9d7a318 100644
--- a/src/main/java/com/diamante/serverlist/InfoDumper.java
+++ b/src/main/java/com/diamante/serverlist/InfoDumper.java
@@ -62,18 +62,8 @@ public class InfoDumper {
obj.put("magic", magicBE);
obj.put("players", playersBE);
obj.put("sv_maxClients", maxPlayersBE);
+ obj.put("info", infoString);
- saveJSONFile(String.format("stats_%d.json", Math.abs(server.hashCode())), obj);
- }
-
- public static void saveJSONFile(String fileName, JSONObject obj) {
- try {
- var writer = new BufferedWriter(new FileWriter(fileName));
- writer.write(obj.toJSONString());
- writer.close();
- }
- catch (IOException ex) {
- System.err.println("saveJSONFile: IOException while writing a JSON file");
- }
+ Utils.saveJSONFile(String.format("stats_%d.json", Math.abs(server.hashCode())), obj);
}
}
diff --git a/src/main/java/com/diamante/serverlist/MasterServerPinger.java b/src/main/java/com/diamante/serverlist/MasterServerPinger.java
index 3dc519d..d7d71b7 100644
--- a/src/main/java/com/diamante/serverlist/MasterServerPinger.java
+++ b/src/main/java/com/diamante/serverlist/MasterServerPinger.java
@@ -23,6 +23,9 @@ import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
/**
*
* @author Diamante
@@ -72,7 +75,7 @@ public class MasterServerPinger {
int count = input.read(bytes);
out.write(bytes, 0, count);
-
+
System.out.println("readReplyFromMaster: finished reading bytes from socket");
}
catch (IOException ex) {
@@ -102,6 +105,40 @@ public class MasterServerPinger {
System.out.println(String.format("readReplyFromMaster: got %d servers", serverCountBE));
+ var root = new JSONObject();
+ var serverArray = new JSONArray();
+
+ // Process server data
+ for (int i = 4; i < bytes.length; i += 6) {
+ if (i + 6 > bytes.length) {
+ System.err.println("readReplyFromMaster: Incomplete server data detected");
+ break;
+ }
+
+ byte[] ipBytesLE = new byte[4];
+ System.arraycopy(bytes, i, ipBytesLE, 0, 4);
+ var ipBytesBE = Utils.longSwap(ipBytesLE);
+
+ var ipAddress = Utils.bytesToIP(ipBytesBE);
+
+ var portBytesLE = new byte[2];
+ System.arraycopy(bytes, i + 4, portBytesLE, 0, 2);
+ var port = ((portBytesLE[1] & 0xFF) << 8) | (portBytesLE[0] & 0xFF);
+
+ System.out.println(String.format("Server: %s:%d", ipAddress, port));
+
+ var serverObject = new JSONObject();
+ serverObject.put("IP", ipAddress);
+ serverObject.put("port", port);
+
+ serverArray.add(serverObject);
+ }
+
+ root.put("totalServers", serverCountBE);
+ root.put("servers", serverArray);
+
+ Utils.saveJSONFile(String.format("server_dump_%d.json", System.currentTimeMillis() / 1000L), root);
+
try {
out.close();
}
diff --git a/src/main/java/com/diamante/serverlist/Server.java b/src/main/java/com/diamante/serverlist/Server.java
index c73f945..ed9ec8c 100644
--- a/src/main/java/com/diamante/serverlist/Server.java
+++ b/src/main/java/com/diamante/serverlist/Server.java
@@ -105,6 +105,7 @@ public class Server {
hash = 71 * hash + Objects.hashCode(this.address);
hash = 71 * hash + Objects.hashCode(this.netPort);
hash = 71 * hash + Objects.hashCode(this.version);
+ hash = 71 * hash + Objects.hashCode(this.time);
return hash;
}
diff --git a/src/main/java/com/diamante/serverlist/Utils.java b/src/main/java/com/diamante/serverlist/Utils.java
index 4950740..88a7f64 100644
--- a/src/main/java/com/diamante/serverlist/Utils.java
+++ b/src/main/java/com/diamante/serverlist/Utils.java
@@ -16,15 +16,19 @@
*/
package com.diamante.serverlist;
-import java.net.InetAddress;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.BufferOverflowException;
import java.nio.ReadOnlyBufferException;
-
import java.nio.ByteBuffer;
+import org.json.simple.JSONObject;
+
/**
*
* @author Diamante
@@ -43,7 +47,7 @@ public class Utils {
public static final int OLD_CLIENT_MAGIC = 1414022477; // THEM
public static final int NEW_CLIENT_MAGIC = 1129268293;
-
+
public static final int CLIENT_VERSION = 17039893;
public static boolean isServerMagic(int magic) {
@@ -151,4 +155,27 @@ public class Utils {
return null;
}
}
+
+ public static String bytesToIP(int in) {
+ String ipAddress = String.format(
+ "%d.%d.%d.%d",
+ (in & 0xff),
+ (in >> 8 & 0xff),
+ (in >> 16 & 0xff),
+ (in >> 24 & 0xff)
+ );
+
+ return ipAddress;
+ }
+
+ public static void saveJSONFile(String fileName, JSONObject obj) {
+ try {
+ var writer = new BufferedWriter(new FileWriter(fileName));
+ writer.write(obj.toJSONString());
+ writer.close();
+ }
+ catch (IOException ex) {
+ System.err.println("saveJSONFile: IOException while writing a JSON file");
+ }
+ }
}