From 4600303f96f46bb85f4bab6a3af58893161c3e9e Mon Sep 17 00:00:00 2001 From: LOOHP Date: Tue, 4 Aug 2020 23:44:58 +0800 Subject: [PATCH] Added some message, fixed problem with packet reading --- src/com/loohp/limbo/DeclareCommands.java | 32 +++++++ src/com/loohp/limbo/Limbo.java | 38 +++++++- .../loohp/limbo/Server/ClientConnection.java | 48 ++++++++-- .../loohp/limbo/Server/KeepAliveSender.java | 9 +- .../Packets/PacketPlayInTabComplete.java | 30 +++++++ .../Packets/PacketPlayOutDeclareCommands.java | 30 +++++++ .../Packets/PacketPlayOutTabComplete.java | 87 +++++++++++++++++++ .../loohp/limbo/Server/ServerConnection.java | 2 +- .../loohp/limbo/Utils/CustomStringUtils.java | 27 ++++++ src/com/loohp/limbo/Utils/NetworkUtils.java | 36 ++++++++ src/mapping.json | 7 +- 11 files changed, 330 insertions(+), 16 deletions(-) create mode 100644 src/com/loohp/limbo/DeclareCommands.java create mode 100644 src/com/loohp/limbo/Server/Packets/PacketPlayInTabComplete.java create mode 100644 src/com/loohp/limbo/Server/Packets/PacketPlayOutDeclareCommands.java create mode 100644 src/com/loohp/limbo/Server/Packets/PacketPlayOutTabComplete.java create mode 100644 src/com/loohp/limbo/Utils/NetworkUtils.java diff --git a/src/com/loohp/limbo/DeclareCommands.java b/src/com/loohp/limbo/DeclareCommands.java new file mode 100644 index 0000000..5eed47f --- /dev/null +++ b/src/com/loohp/limbo/DeclareCommands.java @@ -0,0 +1,32 @@ +package com.loohp.limbo; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import com.loohp.limbo.Server.Packets.PacketPlayOutDeclareCommands; +import com.loohp.limbo.Utils.DataTypeIO; + +public class DeclareCommands { + + public static PacketPlayOutDeclareCommands getDeclareCommandPacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + DataOutputStream output = new DataOutputStream(buffer); + + DataTypeIO.writeVarInt(output, 2); + + output.writeByte(0); + DataTypeIO.writeVarInt(output, 1); + DataTypeIO.writeVarInt(output, 1); + + output.writeByte(1 | 0x04); + DataTypeIO.writeVarInt(output, 0); + DataTypeIO.writeString(output, "spawn", StandardCharsets.UTF_8); + + DataTypeIO.writeVarInt(output, 0); + + return new PacketPlayOutDeclareCommands(buffer.toByteArray()); + } + +} diff --git a/src/com/loohp/limbo/Limbo.java b/src/com/loohp/limbo/Limbo.java index 5e68755..311a9cf 100644 --- a/src/com/loohp/limbo/Limbo.java +++ b/src/com/loohp/limbo/Limbo.java @@ -27,6 +27,7 @@ import com.loohp.limbo.Server.Packets.Packet; import com.loohp.limbo.Server.Packets.PacketIn; import com.loohp.limbo.Server.Packets.PacketOut; import com.loohp.limbo.Utils.ImageUtils; +import com.loohp.limbo.Utils.NetworkUtils; import com.loohp.limbo.World.Schematic; import com.loohp.limbo.World.World; @@ -74,6 +75,12 @@ public class Limbo { } } properties = new ServerProperties(sp); + + if (!properties.isBungeecord()) { + console.sendMessage("If you are using bungeecord, consider turning that on in the settings!"); + } else { + console.sendMessage("Starting Limbo server in bungeecord mode!"); + } String mappingName = "mapping.json"; File mappingFile = new File(mappingName); @@ -85,9 +92,12 @@ public class Limbo { } } + console.sendMessage("Loading packet id mappings from mapping.json ..."); + JSONObject json = (JSONObject) new JSONParser().parse(new FileReader(mappingFile)); String classPrefix = Packet.class.getName().substring(0, Packet.class.getName().lastIndexOf(".") + 1); + int mappingsCount = 0; Map> HandshakeIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("HandshakeIn")).keySet()) { @@ -95,6 +105,7 @@ public class Limbo { HandshakeIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("HandshakeIn")).get(key))); } Packet.setHandshakeIn(HandshakeIn); + mappingsCount += HandshakeIn.size(); Map> StatusIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("StatusIn")).keySet()) { @@ -102,6 +113,7 @@ public class Limbo { StatusIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("StatusIn")).get(key))); } Packet.setStatusIn(StatusIn); + mappingsCount += StatusIn.size(); Map, Integer> StatusOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("StatusOut")).keySet()) { @@ -109,6 +121,7 @@ public class Limbo { StatusOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("StatusOut")).get(key))); } Packet.setStatusOut(StatusOut); + mappingsCount += StatusOut.size(); Map> LoginIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("LoginIn")).keySet()) { @@ -116,6 +129,7 @@ public class Limbo { LoginIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("LoginIn")).get(key))); } Packet.setLoginIn(LoginIn); + mappingsCount += LoginIn.size(); Map, Integer> LoginOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("LoginOut")).keySet()) { @@ -123,6 +137,7 @@ public class Limbo { LoginOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("LoginOut")).get(key))); } Packet.setLoginOut(LoginOut); + mappingsCount += LoginOut.size(); Map> PlayIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("PlayIn")).keySet()) { @@ -130,6 +145,7 @@ public class Limbo { PlayIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("PlayIn")).get(key))); } Packet.setPlayIn(PlayIn); + mappingsCount += PlayIn.size(); Map, Integer> PlayOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("PlayOut")).keySet()) { @@ -137,27 +153,43 @@ public class Limbo { PlayOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("PlayOut")).get(key))); } Packet.setPlayOut(PlayOut); + mappingsCount += PlayOut.size(); + + console.sendMessage("Loaded all " + mappingsCount + " packet id mappings!"); worlds.add(loadDefaultWorld()); Location spawn = properties.getWorldSpawn(); properties.setWorldSpawn(new Location(getWorld(properties.getLevelName().getKey()), spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getYaw(), spawn.getPitch())); + if (!NetworkUtils.available(properties.getServerPort())) { + console.sendMessage(""); + console.sendMessage("*****FAILED TO BIND PORT [" + properties.getServerPort() + "]*****"); + console.sendMessage("*****PORT ALREADY IN USE*****"); + console.sendMessage("*****PERHAPS ANOTHER INSTANCE OF THE SERVER IS ALREADY RUNNING?*****"); + console.sendMessage(""); + System.exit(2); + } + server = new ServerConnection(properties.getServerIp(), properties.getServerPort()); console.run(); } - private World loadDefaultWorld() throws IOException { + private World loadDefaultWorld() throws IOException { + console.sendMessage("Loading world " + properties.getLevelName() + " with the schematic file " + properties.getSchemFileName() + " ..."); + File schem = new File(properties.getSchemFileName()); if (!schem.exists()) { - System.out.println("Schemetic file " + properties.getSchemFileName() + " for world " + properties.getLevelName() + " not found!"); + console.sendMessage("Schemetic file " + properties.getSchemFileName() + " for world " + properties.getLevelName() + " not found!"); + console.sendMessage("Server will exit!"); + System.exit(1); return null; } World world = Schematic.toWorld(properties.getLevelName().getKey(), (CompoundTag) NBTUtil.read(schem).getTag()); - System.out.println("Loaded world " + properties.getLevelName() + " with the schematic file " + properties.getSchemFileName()); + console.sendMessage("Loaded world " + properties.getLevelName() + "!"); return world; } diff --git a/src/com/loohp/limbo/Server/ClientConnection.java b/src/com/loohp/limbo/Server/ClientConnection.java index e4e4e5a..336eff6 100644 --- a/src/com/loohp/limbo/Server/ClientConnection.java +++ b/src/com/loohp/limbo/Server/ClientConnection.java @@ -6,10 +6,13 @@ import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; +import com.loohp.limbo.DeclareCommands; import com.loohp.limbo.Limbo; import com.loohp.limbo.File.ServerProperties; import com.loohp.limbo.Location.Location; @@ -25,6 +28,8 @@ import com.loohp.limbo.Server.Packets.PacketPlayInKeepAlive; import com.loohp.limbo.Server.Packets.PacketPlayInPosition; import com.loohp.limbo.Server.Packets.PacketPlayInPositionAndLook; import com.loohp.limbo.Server.Packets.PacketPlayInRotation; +import com.loohp.limbo.Server.Packets.PacketPlayInTabComplete; +import com.loohp.limbo.Server.Packets.PacketPlayOutDeclareCommands; import com.loohp.limbo.Server.Packets.PacketPlayOutDisconnect; import com.loohp.limbo.Server.Packets.PacketPlayOutLogin; import com.loohp.limbo.Server.Packets.PacketPlayOutMapChunk; @@ -37,6 +42,8 @@ import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoData.Pla import com.loohp.limbo.Server.Packets.PacketPlayOutPositionAndLook; import com.loohp.limbo.Server.Packets.PacketPlayOutShowPlayerSkins; import com.loohp.limbo.Server.Packets.PacketPlayOutSpawnPosition; +import com.loohp.limbo.Server.Packets.PacketPlayOutTabComplete; +import com.loohp.limbo.Server.Packets.PacketPlayOutTabComplete.TabCompleteMatches; import com.loohp.limbo.Server.Packets.PacketPlayOutUpdateViewPosition; import com.loohp.limbo.Server.Packets.PacketStatusInPing; import com.loohp.limbo.Server.Packets.PacketStatusInRequest; @@ -73,7 +80,8 @@ public class ClientConnection extends Thread { private Player player; private long lastKeepAlivePayLoad; - private DataOutputStream output; + protected DataOutputStream output; + protected DataInputStream input; private InetAddress iNetAddress; @@ -156,7 +164,7 @@ public class ClientConnection extends Thread { state = ClientState.HANDSHAKE; try { client_socket.setKeepAlive(true); - DataInputStream input = new DataInputStream(client_socket.getInputStream()); + input = new DataInputStream(client_socket.getInputStream()); output = new DataOutputStream(client_socket.getOutputStream()); DataTypeIO.readVarInt(input); @@ -236,6 +244,8 @@ public class ClientConnection extends Thread { Limbo.getInstance().addPlayer(player); break; + } else { + input.skipBytes(size - DataTypeIO.getVarIntLength(packetId)); } } break; @@ -299,13 +309,15 @@ public class ClientConnection extends Thread { String str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort() + "|" + player.getName(); Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!"); + PacketPlayOutDeclareCommands declare = DeclareCommands.getDeclareCommandPacket(); + sendPacket(declare); + while (client_socket.isConnected()) { - try { + try { int size = DataTypeIO.readVarInt(input); int packetId = DataTypeIO.readVarInt(input); Class packetType = Packet.getPlayIn().get(packetId); - //System.out.println(packetId); - + //System.out.println(packetType); if (packetType == null) { input.skipBytes(size - DataTypeIO.getVarIntLength(packetId)); } else if (packetType.equals(PacketPlayInPositionAndLook.class)) { @@ -332,6 +344,30 @@ public class ClientConnection extends Thread { Limbo.getInstance().getConsole().sendMessage("Incorrect Payload recieved in KeepAlive packet for player " + player.getName()); break; } + } else if (packetType.equals(PacketPlayInTabComplete.class)) { + PacketPlayInTabComplete request = new PacketPlayInTabComplete(input); + String[] command = CustomStringUtils.splitStringToArgs(request.getText()); + System.out.println(request.getText()); + List matches = new ArrayList(); + + TabCompleteMatches spawn = new TabCompleteMatches("spawn", new TextComponent(ChatColor.GOLD + "Teleports you back to the Limbo spawn!")); + + switch (command.length) { + case 0: + matches.add(spawn); + break; + case 1: + if ("spawn".startsWith(command[0])) { + matches.add(spawn); + } + break; + } + + int start = CustomStringUtils.getIndexOfArg(request.getText(), command.length - 1); + int length = command[command.length - 1].length(); + + PacketPlayOutTabComplete response = new PacketPlayOutTabComplete(request.getId(), start, length, matches.toArray(new TabCompleteMatches[matches.size()])); + sendPacket(response); } else if (packetType.equals(PacketPlayInChat.class)) { PacketPlayInChat chat = new PacketPlayInChat(input); if (chat.getMessage().startsWith("/")) { @@ -349,6 +385,8 @@ public class ClientConnection extends Thread { each.sendMessage(message); } } + } else { + input.skipBytes(size - DataTypeIO.getVarIntLength(packetId)); } } catch (Exception e) { diff --git a/src/com/loohp/limbo/Server/KeepAliveSender.java b/src/com/loohp/limbo/Server/KeepAliveSender.java index 2ac5d66..09828a5 100644 --- a/src/com/loohp/limbo/Server/KeepAliveSender.java +++ b/src/com/loohp/limbo/Server/KeepAliveSender.java @@ -1,6 +1,5 @@ package com.loohp.limbo.Server; -import java.io.DataOutputStream; import java.io.IOException; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -12,9 +11,10 @@ import com.loohp.limbo.Utils.DataTypeIO; public class KeepAliveSender extends Thread { - private Random random = new Random(); + private Random random; public KeepAliveSender() { + random = new Random(); start(); } @@ -25,11 +25,10 @@ public class KeepAliveSender extends Thread { for (ClientConnection client : Limbo.getInstance().getServerConnection().getClients()) { if (client.getClientState().equals(ClientState.PLAY)) { try { - DataOutputStream output = new DataOutputStream(client.getSocket().getOutputStream()); PacketPlayOutKeepAlive packet = new PacketPlayOutKeepAlive(random.nextLong()); byte[] packetByte = packet.serializePacket(); - DataTypeIO.writeVarInt(output, packetByte.length); - output.write(packetByte); + DataTypeIO.writeVarInt(client.output, packetByte.length); + client.output.write(packetByte); client.setLastKeepAlivePayLoad(packet.getPayload()); } catch (IOException ignore) {} } diff --git a/src/com/loohp/limbo/Server/Packets/PacketPlayInTabComplete.java b/src/com/loohp/limbo/Server/Packets/PacketPlayInTabComplete.java new file mode 100644 index 0000000..670b5c9 --- /dev/null +++ b/src/com/loohp/limbo/Server/Packets/PacketPlayInTabComplete.java @@ -0,0 +1,30 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.DataInputStream; +import java.io.IOException; + +import com.loohp.limbo.Utils.DataTypeIO; + +public class PacketPlayInTabComplete extends PacketIn { + + private int id; + private String text; + + public PacketPlayInTabComplete(int id, String text) { + this.id = id; + this.text = text; + } + + public PacketPlayInTabComplete(DataInputStream in) throws IOException { + this(DataTypeIO.readVarInt(in), DataTypeIO.readString(in)); + } + + public int getId() { + return id; + } + + public String getText() { + return text; + } + +} diff --git a/src/com/loohp/limbo/Server/Packets/PacketPlayOutDeclareCommands.java b/src/com/loohp/limbo/Server/Packets/PacketPlayOutDeclareCommands.java new file mode 100644 index 0000000..bc29ad4 --- /dev/null +++ b/src/com/loohp/limbo/Server/Packets/PacketPlayOutDeclareCommands.java @@ -0,0 +1,30 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class PacketPlayOutDeclareCommands extends PacketOut { + + private byte[] data; + + public PacketPlayOutDeclareCommands(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + + @Override + public byte[] serializePacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(Packet.getPlayOut().get(getClass())); + output.write(data); + + return buffer.toByteArray(); + } + +} diff --git a/src/com/loohp/limbo/Server/Packets/PacketPlayOutTabComplete.java b/src/com/loohp/limbo/Server/Packets/PacketPlayOutTabComplete.java new file mode 100644 index 0000000..1ebf700 --- /dev/null +++ b/src/com/loohp/limbo/Server/Packets/PacketPlayOutTabComplete.java @@ -0,0 +1,87 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import com.loohp.limbo.Utils.DataTypeIO; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.chat.ComponentSerializer; + +public class PacketPlayOutTabComplete extends PacketOut { + + private int id; + private int start; + private int length; + private TabCompleteMatches[] matches; + + public PacketPlayOutTabComplete(int id, int start, int length, TabCompleteMatches... matches) { + this.id = id; + this.start = start; + this.length = length; + } + + public int getId() { + return id; + } + + public int getStart() { + return start; + } + + public int getLength() { + return length; + } + + public TabCompleteMatches[] getMatches() { + return matches; + } + + @Override + public byte[] serializePacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(Packet.getPlayOut().get(getClass())); + DataTypeIO.writeVarInt(output, id); + DataTypeIO.writeVarInt(output, start); + DataTypeIO.writeVarInt(output, length); + DataTypeIO.writeVarInt(output, matches.length); + + for (TabCompleteMatches match : matches) { + DataTypeIO.writeString(output, match.getMatch(), StandardCharsets.UTF_8); + if (match.getTooltip().isPresent()) { + output.writeBoolean(true); + DataTypeIO.writeString(output, ComponentSerializer.toString(match.getTooltip().get()), StandardCharsets.UTF_8); + } else { + output.writeBoolean(false); + } + } + + return buffer.toByteArray(); + } + + public static class TabCompleteMatches { + + private String match; + private Optional tooltip; + + public TabCompleteMatches(String match, BaseComponent... tooltip) { + this.match = match; + this.tooltip = tooltip.length > 0 ? Optional.of(tooltip) : Optional.empty(); + } + + public String getMatch() { + return match; + } + + public Optional getTooltip() { + return tooltip; + } + + } + +} diff --git a/src/com/loohp/limbo/Server/ServerConnection.java b/src/com/loohp/limbo/Server/ServerConnection.java index 59952a8..d6c9133 100644 --- a/src/com/loohp/limbo/Server/ServerConnection.java +++ b/src/com/loohp/limbo/Server/ServerConnection.java @@ -27,7 +27,7 @@ public class ServerConnection extends Thread { public void run() { try { serverSocket = new ServerSocket(port, 50, InetAddress.getByName(ip)); - System.out.println("Server listening on [" + serverSocket.getInetAddress().getHostName() + ":" + serverSocket.getLocalPort() + "]"); + System.out.println("Limbo server listening on /" + serverSocket.getInetAddress().getHostName() + ":" + serverSocket.getLocalPort()); while (true) { Socket connection = serverSocket.accept(); //String str = connection.getInetAddress().getHostName() + ":" + connection.getPort(); diff --git a/src/com/loohp/limbo/Utils/CustomStringUtils.java b/src/com/loohp/limbo/Utils/CustomStringUtils.java index c83fb95..3f1e009 100644 --- a/src/com/loohp/limbo/Utils/CustomStringUtils.java +++ b/src/com/loohp/limbo/Utils/CustomStringUtils.java @@ -37,4 +37,31 @@ public class CustomStringUtils { return tokens.toArray(new String[tokens.size()]); } + public static int getIndexOfArg(String str, int ordinal) { + StringBuilder sb = new StringBuilder(); + + boolean insideQuote = false; + + int pos = 0; + int found = 0; + for (char c : str.toCharArray()) { + if (c == '"') { + insideQuote = !insideQuote; + } else if (c == ' ' && !insideQuote) { + if (sb.length() > 0) { + found++; + } + sb.delete(0, sb.length()); + } else { + sb.append(c); + } + if (found == ordinal) { + return pos; + } + pos++; + } + + return -1; + } + } diff --git a/src/com/loohp/limbo/Utils/NetworkUtils.java b/src/com/loohp/limbo/Utils/NetworkUtils.java new file mode 100644 index 0000000..42a601d --- /dev/null +++ b/src/com/loohp/limbo/Utils/NetworkUtils.java @@ -0,0 +1,36 @@ +package com.loohp.limbo.Utils; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.ServerSocket; + +public class NetworkUtils { + + public static boolean available(int port) { + ServerSocket ss = null; + DatagramSocket ds = null; + try { + ss = new ServerSocket(port); + ss.setReuseAddress(true); + ds = new DatagramSocket(port); + ds.setReuseAddress(true); + return true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (ds != null) { + ds.close(); + } + + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return false; + } + +} diff --git a/src/mapping.json b/src/mapping.json index 7f01fe4..806e36e 100644 --- a/src/mapping.json +++ b/src/mapping.json @@ -17,7 +17,8 @@ "0x13": "PacketPlayInPositionAndLook", "0x12": "PacketPlayInPosition", "0x14": "PacketPlayInRotation", - "0x0B": "PacketPlayInPluginMessaging" + "0x0B": "PacketPlayInPluginMessaging", + "0x06": "PacketPlayInTabComplete" }, "PlayOut": { "PacketPlayOutLogin": "0x25", @@ -31,7 +32,9 @@ "PacketPlayOutUpdateViewPosition": "0x40", "PacketPlayOutShowPlayerSkins": "0x44", "PacketPlayOutDisconnect": "0x1A", - "PacketPlayOutPluginMessaging": "0x17" + "PacketPlayOutPluginMessaging": "0x17", + "PacketPlayOutTabComplete": "0x10", + "PacketPlayOutDeclareCommands": "0x11" }, "StatusIn": { "0x01": "PacketStatusInPing",