diff --git a/src/main/java/net/md_5/bungee/BungeeCord.java b/src/main/java/net/md_5/bungee/BungeeCord.java index c33ddbea..34e537c9 100644 --- a/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/src/main/java/net/md_5/bungee/BungeeCord.java @@ -14,7 +14,9 @@ import java.util.concurrent.Executors; import static net.md_5.bungee.Logger.$; import net.md_5.bungee.command.Command; import net.md_5.bungee.command.CommandEnd; +import net.md_5.bungee.command.CommandList; import net.md_5.bungee.command.CommandSender; +import net.md_5.bungee.command.CommandServer; import net.md_5.bungee.command.ConsoleCommandSender; public class BungeeCord { @@ -58,6 +60,8 @@ public class BungeeCord { { commandMap.put("end", new CommandEnd()); + commandMap.put("glist", new CommandList()); + commandMap.put("server", new CommandServer()); } public static void main(String[] args) throws IOException { diff --git a/src/main/java/net/md_5/bungee/Configuration.java b/src/main/java/net/md_5/bungee/Configuration.java index e9db38f4..7ab5316f 100644 --- a/src/main/java/net/md_5/bungee/Configuration.java +++ b/src/main/java/net/md_5/bungee/Configuration.java @@ -194,10 +194,11 @@ public class Configuration { public InetSocketAddress getServer(String name) { String hostline = (name == null) ? defaultServerName : name; - if (hostline != null) { - return Util.getAddr(hostline); + String server = servers.get(hostline); + if (server != null) { + return Util.getAddr(server); } else { - throw new IllegalArgumentException("No server by name " + name); + return Util.getAddr(servers.get(defaultServerName)); } } diff --git a/src/main/java/net/md_5/bungee/EntityMap.java b/src/main/java/net/md_5/bungee/EntityMap.java new file mode 100644 index 00000000..75f9f666 --- /dev/null +++ b/src/main/java/net/md_5/bungee/EntityMap.java @@ -0,0 +1,61 @@ +package net.md_5.bungee; + +public class EntityMap { + + public final static int[][] entityIds = new int[256][]; + + static { + entityIds[0x05] = new int[]{1}; + entityIds[0x07] = new int[]{1, 5}; + entityIds[0x11] = new int[]{1}; + entityIds[0x12] = new int[]{1}; + entityIds[0x13] = new int[]{1}; + entityIds[0x14] = new int[]{1}; + entityIds[0x15] = new int[]{1}; + entityIds[0x16] = new int[]{1, 5}; + entityIds[0x17] = new int[]{1}; + entityIds[0x18] = new int[]{1}; + entityIds[0x19] = new int[]{1}; + entityIds[0x1C] = new int[]{1}; + entityIds[0x1E] = new int[]{1}; + entityIds[0x1F] = new int[]{1}; + entityIds[0x20] = new int[]{1}; + entityIds[0x21] = new int[]{1}; + entityIds[0x22] = new int[]{1}; + entityIds[0x26] = new int[]{1}; + entityIds[0x27] = new int[]{1, 5}; + entityIds[0x28] = new int[]{1}; + entityIds[0x47] = new int[]{1}; + } + + public static void rewrite(byte[] packet, int oldId, int newId) { + int packetId = Util.getId(packet); + if (packetId == 0x1D) { // bulk entity + for (int pos = 2; pos < packet.length; pos += 4) { + if (oldId == readInt(packet, pos)) { + setInt(packet, pos, newId); + } + } + } else { + int[] idArray = entityIds[packetId]; + if (idArray != null) { + for (int pos : idArray) { + if (oldId == readInt(packet, pos)) { + setInt(packet, pos, newId); + } + } + } + } + } + + private static void setInt(byte[] buf, int pos, int i) { + buf[pos] = (byte) (i >> 24); + buf[pos + 1] = (byte) (i >> 16); + buf[pos + 2] = (byte) (i >> 8); + buf[pos + 3] = (byte) i; + } + + private static int readInt(byte[] buf, int pos) { + return (((buf[pos] & 0xFF) << 24) | ((buf[pos + 1] & 0xFF) << 16) | ((buf[pos + 2] & 0xFF) << 8) | buf[pos + 3] & 0xFF); + } +} diff --git a/src/main/java/net/md_5/bungee/UserConnection.java b/src/main/java/net/md_5/bungee/UserConnection.java index 172f96af..27545722 100644 --- a/src/main/java/net/md_5/bungee/UserConnection.java +++ b/src/main/java/net/md_5/bungee/UserConnection.java @@ -6,18 +6,28 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import net.md_5.bungee.command.CommandSender; import net.md_5.bungee.packet.DefinedPacket; +import net.md_5.bungee.packet.Packet10HeldItem; +import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.Packet2Handshake; import net.md_5.bungee.packet.Packet3Chat; +import net.md_5.bungee.packet.Packet46GameState; +import net.md_5.bungee.packet.Packet9Respawn; +import net.md_5.bungee.packet.PacketFAPluginMessage; import net.md_5.bungee.packet.PacketInputStream; -public class UserConnection extends GenericConnection { +public class UserConnection extends GenericConnection implements CommandSender { public final Packet2Handshake handshake; public Queue packetQueue = new ConcurrentLinkedQueue<>(); private ServerConnection server; private UpstreamBridge upBridge; private DownstreamBridge downBridge; + // reconnect stuff + private Packet10HeldItem heldItem; + private int clientEntityId; + private int serverEntityId; public UserConnection(Socket socket, PacketInputStream in, OutputStream out, Packet2Handshake handshake) { super(socket, in, out); @@ -25,16 +35,37 @@ public class UserConnection extends GenericConnection { username = handshake.username; } + public void connect(String server) { + InetSocketAddress addr = BungeeCord.instance.config.getServer(server); + if (addr == null) { + sendMessage(ChatColor.RED + "Specified server does not exist"); + return; + } + connect(addr); + } + public void connect(InetSocketAddress serverAddr) { try { ServerConnection newServer = ServerConnection.connect(serverAddr, handshake, true); + if (downBridge != null) { + downBridge.interrupt(); + } if (server == null) { + clientEntityId = newServer.loginPacket.entityId; out.write(newServer.loginPacket.getPacket()); upBridge = new UpstreamBridge(); upBridge.start(); - } - if (downBridge != null) { - downBridge.interrupt(); + } else { + server.disconnect("Quitting"); + out.write(new Packet9Respawn((byte) 1, (byte) 0, (byte) 0, (short) 256, "DEFAULT").getPacket()); + out.write(new Packet9Respawn((byte) -1, (byte) 0, (byte) 0, (short) 256, "DEFAULT").getPacket()); + out.write(new Packet46GameState((byte) 2, (byte) 0).getPacket()); + Packet1Login login = newServer.loginPacket; + serverEntityId = login.entityId; + out.write(new Packet9Respawn(login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType).getPacket()); + if (heldItem != null) { + newServer.out.write(heldItem.getPacket()); + } } downBridge = new DownstreamBridge(); server = newServer; @@ -70,6 +101,11 @@ public class UserConnection extends GenericConnection { } } + @Override + public void sendMessage(String message) { + packetQueue.add(new Packet3Chat(message)); + } + private class UpstreamBridge extends Thread { public UpstreamBridge() { @@ -81,7 +117,23 @@ public class UserConnection extends GenericConnection { while (!interrupted()) { try { byte[] packet = in.readPacket(); - server.out.write(packet); + boolean sendPacket = true; + + int id = Util.getId(packet); + if (id == 0x03) { + Packet3Chat chat = new Packet3Chat(packet); + String message = chat.message; + if (message.startsWith("/")) { + sendPacket = !BungeeCord.instance.dispatchCommand(message.substring(1), UserConnection.this); + } + } else if (id == 0x10) { + heldItem = new Packet10HeldItem(packet); + } + + EntityMap.rewrite(packet, clientEntityId, serverEntityId); + if (sendPacket && !server.socket.isClosed()) { + server.out.write(packet); + } } catch (IOException ex) { destory("Reached end of stream"); } catch (Exception ex) { @@ -102,10 +154,30 @@ public class UserConnection extends GenericConnection { while (!interrupted()) { try { byte[] packet = server.in.readPacket(); - out.write(packet); - out.flush(); + boolean sendPacket = true; + + int id = Util.getId(packet); + if (id == 0xFA) { + PacketFAPluginMessage message = new PacketFAPluginMessage(packet); + if (message.tag.equals("RubberBand")) { + String server = new String(message.data); + connect(server); + sendPacket = false; + } + } + + while (!packetQueue.isEmpty()) { + DefinedPacket p = packetQueue.poll(); + if (p != null) { + out.write(p.getPacket()); + } + } + + EntityMap.rewrite(packet, serverEntityId, clientEntityId); + if (sendPacket) { + out.write(packet); + } } catch (IOException ex) { - destory("Reached end of stream"); } catch (Exception ex) { destory(Util.exception(ex)); } diff --git a/src/main/java/net/md_5/bungee/command/CommandList.java b/src/main/java/net/md_5/bungee/command/CommandList.java new file mode 100644 index 00000000..44cc3b9d --- /dev/null +++ b/src/main/java/net/md_5/bungee/command/CommandList.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.command; + +import java.util.Collection; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.ChatColor; +import net.md_5.bungee.UserConnection; + +public class CommandList extends Command { + + @Override + public void execute(CommandSender sender, String[] args) { + StringBuilder users = new StringBuilder(); + Collection connections = BungeeCord.instance.connections.values(); + + for (UserConnection con : connections) { + users.append(con.username); + users.append(ChatColor.RESET.toString()); + users.append(", "); + } + + users.setLength(users.length() - 2); + sender.sendMessage(ChatColor.BLUE + "Currently online across all servers (" + connections.size() + "): " + ChatColor.RESET + users); + } +} diff --git a/src/main/java/net/md_5/bungee/command/CommandServer.java b/src/main/java/net/md_5/bungee/command/CommandServer.java new file mode 100644 index 00000000..87748d95 --- /dev/null +++ b/src/main/java/net/md_5/bungee/command/CommandServer.java @@ -0,0 +1,30 @@ +package net.md_5.bungee.command; + +import java.util.Collection; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.ChatColor; +import net.md_5.bungee.UserConnection; + +public class CommandServer extends Command { + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof UserConnection)) { + return; + } + UserConnection con = (UserConnection) sender; + if (args.length <= 0) { + Collection servers = BungeeCord.instance.config.servers.keySet(); + StringBuilder serverList = new StringBuilder(); + for (String server : servers) { + serverList.append(server); + serverList.append(", "); + } + serverList.setLength(serverList.length() - 2); + con.sendMessage(ChatColor.GOLD + "You may connect to the following servers at this time: " + serverList.toString()); + } else { + String server = args[0]; + con.connect(server); + } + } +}