From 81d47810f3f1683e7ab1867f1bd508e82c9725e8 Mon Sep 17 00:00:00 2001 From: LOOHP Date: Sat, 8 Aug 2020 19:36:47 +0800 Subject: [PATCH] Status Response StatusChangeEvent, Properly implement status response, allow changing through event, Supports legacy 0xFE ping --- pom.xml | 6 ++ .../loohp/limbo/Events/StatusPingEvent.java | 81 +++++++++++++++++++ src/com/loohp/limbo/Limbo.java | 61 +++++++++++--- .../loohp/limbo/Server/ClientConnection.java | 28 ++++++- 4 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 src/com/loohp/limbo/Events/StatusPingEvent.java 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);