diff --git a/pom.xml b/pom.xml
index be90d37..44996ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
+ ${project.artifactId}
@@ -64,6 +65,11 @@
NBT
5.5
+
+ com.google.code.gson
+ gson
+ 2.8.5
+
org.yaml
snakeyaml
diff --git a/src/com/loohp/limbo/Events/StatusPingEvent.java b/src/com/loohp/limbo/Events/StatusPingEvent.java
new file mode 100644
index 0000000..5784212
--- /dev/null
+++ b/src/com/loohp/limbo/Events/StatusPingEvent.java
@@ -0,0 +1,81 @@
+package com.loohp.limbo.Events;
+
+import java.awt.image.BufferedImage;
+
+import com.loohp.limbo.Server.ClientConnection;
+
+import net.md_5.bungee.api.chat.BaseComponent;
+
+public class StatusPingEvent extends Event {
+
+ private ClientConnection connection;
+ private String version;
+ private int protocol;
+ private BaseComponent[] motd;
+ private int maxPlayers;
+ private int playersOnline;
+ private BufferedImage favicon;
+
+ public StatusPingEvent(ClientConnection connection, String version, int protocol, BaseComponent[] motd, int maxPlayers, int playersOnline, BufferedImage favicon) {
+ this.connection = connection;
+ this.version = version;
+ this.protocol = protocol;
+ this.motd = motd;
+ this.maxPlayers = maxPlayers;
+ this.playersOnline = playersOnline;
+ this.favicon = favicon;
+ }
+
+ public ClientConnection getConnection() {
+ return connection;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public int getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(int protocol) {
+ this.protocol = protocol;
+ }
+
+ public BaseComponent[] getMotd() {
+ return motd;
+ }
+
+ public void setMotd(BaseComponent[] motd) {
+ this.motd = motd;
+ }
+
+ public int getMaxPlayers() {
+ return maxPlayers;
+ }
+
+ public void setMaxPlayers(int maxPlayers) {
+ this.maxPlayers = maxPlayers;
+ }
+
+ public int getPlayersOnline() {
+ return playersOnline;
+ }
+
+ public void setPlayersOnline(int playersOnline) {
+ this.playersOnline = playersOnline;
+ }
+
+ public BufferedImage getFavicon() {
+ return favicon;
+ }
+
+ public void setFavicon(BufferedImage favicon) {
+ this.favicon = favicon;
+ }
+
+}
diff --git a/src/com/loohp/limbo/Limbo.java b/src/com/loohp/limbo/Limbo.java
index 1aace45..a029c3b 100644
--- a/src/com/loohp/limbo/Limbo.java
+++ b/src/com/loohp/limbo/Limbo.java
@@ -1,6 +1,7 @@
package com.loohp.limbo;
import java.awt.GraphicsEnvironment;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
@@ -11,19 +12,25 @@ import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
import com.loohp.limbo.Commands.CommandSender;
import com.loohp.limbo.Events.EventsManager;
import com.loohp.limbo.File.ServerProperties;
@@ -44,6 +51,8 @@ import com.loohp.limbo.World.Schematic;
import com.loohp.limbo.World.World;
import com.loohp.limbo.World.World.Environment;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.chat.ComponentSerializer;
import net.querz.nbt.io.NBTUtil;
import net.querz.nbt.tag.CompoundTag;
@@ -340,22 +349,48 @@ public class Limbo {
return null;
}
- public String getServerListResponseJson() throws IOException {
- String base = ServerProperties.JSON_BASE_RESPONSE;
- base = base.replace("%VERSION%", properties.getVersionString());
- base = base.replace("%PROTOCOL%", String.valueOf(properties.getProtocol()));
- base = base.replace("%MOTD%", properties.getMotdJson());
- base = base.replace("%MAXPLAYERS%", String.valueOf(properties.getMaxPlayers()));
- base = base.replace("%ONLINECLIENTS%", String.valueOf(getPlayers().size()));
+ @SuppressWarnings("unchecked")
+ public String buildServerListResponseJson(String version, int protocol, BaseComponent[] motd, int maxPlayers, int playersOnline, BufferedImage favicon) throws IOException {
+ JSONObject json = new JSONObject();
+
+ JSONObject versionJson = new JSONObject();
+ versionJson.put("name", version);
+ versionJson.put("protocol", protocol);
+ json.put("version", versionJson);
- if (properties.getFavicon().isPresent()) {
- String icon = "\"favicon\":\"data:image/png;base64," + ImageUtils.imgToBase64String(properties.getFavicon().get(), "png") + "\",";
- base = base.replace("%FAVICON%", icon);
- } else {
- base = base.replace("%FAVICON%", "");
+ JSONObject playersJson = new JSONObject();
+ playersJson.put("max", maxPlayers);
+ playersJson.put("online", playersOnline);
+ json.put("players", playersJson);
+
+ json.put("description", "%MOTD%");
+
+ if (favicon != null) {
+ if (favicon.getWidth() == 64 && favicon.getHeight() == 64) {
+ String base64 = "data:image/png;base64," + ImageUtils.imgToBase64String(favicon, "png");
+ json.put("favicon", base64);
+ } else {
+ console.sendMessage("Server List Favicon must be 64 x 64 in size!");
+ }
}
- return base;
+ JSONObject modInfoJson = new JSONObject();
+ modInfoJson.put("type", "FML");
+ modInfoJson.put("modList", new JSONArray());
+ json.put("modinfo", modInfoJson);
+
+
+ TreeMap treeMap = new TreeMap(String.CASE_INSENSITIVE_ORDER);
+ treeMap.putAll(json);
+
+ Gson g = new GsonBuilder().create();
+
+ return g.toJson(treeMap).replace("\"%MOTD%\"", ComponentSerializer.toString(motd));
+ }
+
+ public String buildLegacyPingResponse(String version, BaseComponent[] motd, int maxPlayers, int playersOnline) {
+ String begin = "§1";
+ return String.join("\00", begin, "127", version, String.join("", Arrays.asList(motd).stream().map(each -> each.toLegacyText()).collect(Collectors.toList())), String.valueOf(playersOnline), String.valueOf(maxPlayers));
}
public void stopServer() {
diff --git a/src/com/loohp/limbo/Server/ClientConnection.java b/src/com/loohp/limbo/Server/ClientConnection.java
index aef5c0b..0ee20cd 100644
--- a/src/com/loohp/limbo/Server/ClientConnection.java
+++ b/src/com/loohp/limbo/Server/ClientConnection.java
@@ -18,6 +18,7 @@ import com.loohp.limbo.Limbo;
import com.loohp.limbo.Events.PlayerJoinEvent;
import com.loohp.limbo.Events.PlayerLoginEvent;
import com.loohp.limbo.Events.PlayerQuitEvent;
+import com.loohp.limbo.Events.StatusPingEvent;
import com.loohp.limbo.File.ServerProperties;
import com.loohp.limbo.Location.Location;
import com.loohp.limbo.Player.Player;
@@ -71,6 +72,7 @@ import net.querz.mca.Chunk;
public class ClientConnection extends Thread {
public enum ClientState {
+ LEGACY,
HANDSHAKE,
STATUS,
LOGIN,
@@ -164,10 +166,26 @@ public class ClientConnection extends Thread {
client_socket.setKeepAlive(true);
input = new DataInputStream(client_socket.getInputStream());
output = new DataOutputStream(client_socket.getOutputStream());
- DataTypeIO.readVarInt(input);
+ int handShakeSize = DataTypeIO.readVarInt(input);
+
+ //legacy ping
+ if (handShakeSize == 0xFE) {
+ state = ClientState.LEGACY;
+ output.writeByte(255);
+ ServerProperties p = Limbo.getInstance().getServerProperties();
+ StatusPingEvent event = (StatusPingEvent) Limbo.getInstance().getEventsManager().callEvent(new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), ComponentSerializer.parse(p.getMotdJson()), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null)));
+ String response = Limbo.getInstance().buildLegacyPingResponse(event.getVersion(), event.getMotd(), event.getMaxPlayers(), event.getPlayersOnline());
+ System.out.println(response);
+ byte[] bytes = response.getBytes(StandardCharsets.UTF_16BE);
+ output.writeShort(response.length());
+ output.write(bytes);
+
+ client_socket.close();
+ state = ClientState.DISCONNECTED;
+ }
- //int handShakeId = DataTypeIO.readVarInt(input);
- DataTypeIO.readVarInt(input);
+ @SuppressWarnings("unused")
+ int handShakeId = DataTypeIO.readVarInt(input);
PacketHandshakingIn handshake = new PacketHandshakingIn(input);
@@ -188,7 +206,9 @@ public class ClientConnection extends Thread {
} else if (packetType.equals(PacketStatusInRequest.class)) {
String str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Handshake Status has pinged");
- PacketStatusOutResponse packet = new PacketStatusOutResponse(Limbo.getInstance().getServerListResponseJson());
+ ServerProperties p = Limbo.getInstance().getServerProperties();
+ StatusPingEvent event = (StatusPingEvent) Limbo.getInstance().getEventsManager().callEvent(new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), ComponentSerializer.parse(p.getMotdJson()), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null)));
+ PacketStatusOutResponse packet = new PacketStatusOutResponse(Limbo.getInstance().buildServerListResponseJson(event.getVersion(), event.getProtocol(), event.getMotd(), event.getMaxPlayers(), event.getPlayersOnline(), event.getFavicon()));
sendPacket(packet);
} else if (packetType.equals(PacketStatusInPing.class)) {
PacketStatusInPing ping = new PacketStatusInPing(input);