diff --git a/.gitignore b/.gitignore index 92725511..fa02ff80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,31 @@ # Eclipse stuff -/.classpath -/.project -/.settings +.classpath/ +.project/ +.settings/ # netbeans -/nbproject -/nbactions.xml -/nb-configuration.xml +nbproject/ +nbactions.xml +nb-configuration.xml # we use maven! -/build.xml +build.xml # maven -/target -/dependency-reduced-pom.xml +target/ +dependency-reduced-pom.xml # vim .*.sw[a-p] # various other potential build files -/build -/bin -/dist -/manifest.mf +build/ +bin/ +dist/ +manifest.mf # Mac filesystem dust -/.DS_Store +.DS_Store/ # intellij *.iml @@ -34,5 +34,5 @@ .idea/ # other files -/*log* +*.log* *.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7ef0f11b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: java +jdk: + - openjdk7 +notifications: + email: false diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 00000000..dce9e57c --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + net.md-5 + bungeecord-parent + 1.4.7-SNAPSHOT + ../pom.xml + + + net.md-5 + bungeecord-api + 1.4.7-SNAPSHOT + jar + + BungeeCord-API + API implemented by the Elastic Portal Suite + + + + com.google.guava + guava + 13.0.1 + compile + + + org.yaml + snakeyaml + 1.11 + compile + + + diff --git a/api/src/main/java/net/md_5/bungee/api/Callback.java b/api/src/main/java/net/md_5/bungee/api/Callback.java new file mode 100644 index 00000000..0cccc175 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/Callback.java @@ -0,0 +1,19 @@ +package net.md_5.bungee.api; + +/** + * Represents a method which may be called once a result has been computed + * asynchronously. + * + * @param the type of result + */ +public interface Callback +{ + + /** + * Called when the result is done. + * + * @param result the result of the computation + * @param error the error(s) that occurred, if any + */ + public void done(V result, Throwable error); +} diff --git a/src/main/java/net/md_5/bungee/ChatColor.java b/api/src/main/java/net/md_5/bungee/api/ChatColor.java similarity index 99% rename from src/main/java/net/md_5/bungee/ChatColor.java rename to api/src/main/java/net/md_5/bungee/api/ChatColor.java index ca157672..a72c01d1 100644 --- a/src/main/java/net/md_5/bungee/ChatColor.java +++ b/api/src/main/java/net/md_5/bungee/api/ChatColor.java @@ -1,4 +1,4 @@ -package net.md_5.bungee; +package net.md_5.bungee.api; import java.util.regex.Pattern; diff --git a/api/src/main/java/net/md_5/bungee/api/CommandSender.java b/api/src/main/java/net/md_5/bungee/api/CommandSender.java new file mode 100644 index 00000000..3596e52f --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/CommandSender.java @@ -0,0 +1,59 @@ +package net.md_5.bungee.api; + +import java.util.Collection; + +public interface CommandSender +{ + + /** + * Get the unique name of this command sender. + * + * @return the senders username + */ + public String getName(); + + /** + * Send a message to this sender. + * + * @param message the message to send + */ + public void sendMessage(String message); + + /** + * Get all groups this user is part of. This returns an unmodifiable + * collection. + * + * @return the users groups + */ + public Collection getGroups(); + + /** + * Adds groups to a this user for the current session only. + * + * @param groups the groups to add + */ + public void addGroups(String... groups); + + /** + * Remove groups from this user for the current session only. + * + * @param groups the groups to remove + */ + public void removeGroups(String... groups); + + /** + * Checks if this user has the specified permission node. + * + * @param permission the node to check + * @return whether they have this node + */ + public boolean hasPermission(String permission); + + /** + * Set a permission node for this user. + * + * @param permission the node to set + * @param value the value of the node + */ + public void setPermission(String permission, boolean value); +} diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java new file mode 100644 index 00000000..1d4522c6 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java @@ -0,0 +1,177 @@ +package net.md_5.bungee.api; + +import net.md_5.bungee.api.plugin.PluginManager; +import com.google.common.base.Preconditions; +import java.util.Collection; +import java.util.Map; +import java.util.logging.Logger; +import lombok.Getter; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.plugin.Plugin; + +public abstract class ProxyServer +{ + + @Getter + private static ProxyServer instance; + + /** + * Sets the proxy instance. This method may only be called once per an + * application. + * + * @param instance the new instance to set + */ + public static void setInstance(ProxyServer instance) + { + Preconditions.checkNotNull(instance, "instance"); + Preconditions.checkArgument(ProxyServer.instance == null, "Instance already set"); + ProxyServer.instance = instance; + } + + /** + * Gets the name of the currently running proxy software. + * + * @return the name of this instance + */ + public abstract String getName(); + + /** + * Gets the version of the currently running proxy software. + * + * @return the version of this instance + */ + public abstract String getVersion(); + + /** + * Gets the main logger which can be used as a suitable replacement for + * {@link System#out} and {@link System#err}. + * + * @return the {@link Logger} instance + */ + public abstract Logger getLogger(); + + /** + * Return all players currently connected. + * + * @return all connected players + */ + public abstract Collection getPlayers(); + + /** + * Gets a connected player via their unique username. + * + * @param name of the player + * @return their player instance + */ + public abstract ProxiedPlayer getPlayer(String name); + + /** + * Get a server by its name. The instance returned will be taken from a + * player currently on that server to facilitate abstract proxy -> server + * actions. + * + * @param name the name to lookup + * @return the associated server + */ + public abstract Server getServer(String name); + + /** + * Return all servers registered to this proxy, keyed by name. Unlike the + * methods in {@link ConfigurationAdapter#getServers()}, this will not + * return a fresh map each time. + * + * @return all registered remote server destinations + */ + public abstract Map getServers(); + + /** + * Get the {@link PluginManager} associated with loading plugins and + * dispatching events. It is recommended that implementations use the + * provided PluginManager class. + * + * @return the plugin manager + */ + public abstract PluginManager getPluginManager(); + + /** + * Returns the currently in use configuration adapter. + * + * @return the used configuration adapter + */ + public abstract ConfigurationAdapter getConfigurationAdapter(); + + /** + * Set the configuration adapter to be used. Must be called from + * {@link Plugin#onLoad()}. + * + * @param adapter the adapter to use + */ + public abstract void setConfigurationAdapter(ConfigurationAdapter adapter); + + /** + * Get the currently in use tab list handle. + * + * @return the tab list handler + */ + public abstract TabListHandler getTabListHandler(); + + /** + * Set the used tab list handler, should not be changed once players have + * connected + * + * @param handler the tab list handler to set + */ + public abstract void setTabListHandler(TabListHandler handler); + + /** + * Get the currently in use reconnect handler. + * + * @return the in use reconnect handler + */ + public abstract ReconnectHandler getReconnectHandler(); + + /** + * Sets the reconnect handler to be used for subsequent connections. + * + * @param handler the new handler + */ + public abstract void setReconnectHandler(ReconnectHandler handler); + + /** + * Gracefully mark this instance for shutdown. + */ + public abstract void stop(); + + /** + * Start this instance so that it may accept connections. + * + * @throws Exception any exception thrown during startup causing the + * instance to fail to boot + */ + public abstract void start() throws Exception; + + /** + * Register a channel for use with plugin messages. This is required by some + * server / client implementations. + * + * @param channel the channel to register + */ + public abstract void registerChannel(String channel); + + /** + * Unregister a previously registered channel. + * + * @param channel the channel to unregister + */ + public abstract void unregisterChannel(String channel); + + /** + * Get an immutable set of all registered plugin channels. + * + * @return registered plugin channels + */ + public abstract Collection getChannels(); +} diff --git a/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java b/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java new file mode 100644 index 00000000..dd417587 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java @@ -0,0 +1,30 @@ +package net.md_5.bungee.api; + +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public interface ReconnectHandler +{ + + /** + * Gets the initial server name for a connecting player. + * + * @param player the connecting player + * @return the server name + */ + public String getServer(ProxiedPlayer player); + + /** + * Save the server of this player before they disconnect so it can be + * retrieved later. + * + * @param player the player to save + */ + public void setServer(ProxiedPlayer player); + + /** + * Save all pending reconnect locations. Whilst not used for database + * connections, this method will be called at a predefined interval to allow + * the saving of reconnect files. + */ + public void save(); +} diff --git a/api/src/main/java/net/md_5/bungee/api/ServerPing.java b/api/src/main/java/net/md_5/bungee/api/ServerPing.java new file mode 100644 index 00000000..d1dda171 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/ServerPing.java @@ -0,0 +1,33 @@ +package net.md_5.bungee.api; + +import lombok.Data; + +/** + * Represents the standard list data returned by opening a server in the + * Minecraft client server list, or hitting it with a packet 0xFE. + */ +@Data +public class ServerPing +{ + + /** + * Numeric protocol version supported by the server. + */ + private final byte protocolVersion; + /** + * Human readable game version. + */ + private final String gameVersion; + /** + * Server MOTD. + */ + private final String motd; + /** + * Current amount of players on the server. + */ + private final String currentPlayers; + /** + * Max amount of players the server will allow. + */ + private final String maxPlayers; +} diff --git a/api/src/main/java/net/md_5/bungee/api/TabListHandler.java b/api/src/main/java/net/md_5/bungee/api/TabListHandler.java new file mode 100644 index 00000000..e4db4e6f --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/TabListHandler.java @@ -0,0 +1,48 @@ +package net.md_5.bungee.api; + +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public interface TabListHandler +{ + + /** + * Called when a player first connects to the proxy. + * + * @param player the connecting player + */ + public void onConnect(ProxiedPlayer player); + + /** + * Called when a player changes their connected server. + * + * @param player the player who changed servers + */ + public void onServerChange(ProxiedPlayer player); + + /** + * Called when a players ping changes. The new ping will have not updated in + * the player instance until this method returns. + * + * @param player the player who's ping changed + * @param ping the player's new ping. + */ + public void onPingChange(ProxiedPlayer player, int ping); + + /** + * Called when a player disconnects. + * + * @param player the disconnected player + */ + public void onDisconnect(ProxiedPlayer player); + + /** + * Called when a list update packet is sent from server to client. + * + * @param player receiving this packet + * @param name the player which this packet is relevant to + * @param online whether the subject player is online + * @param ping ping of the subject player + * @return whether to send the packet to the client + */ + public boolean onListUpdate(ProxiedPlayer player, String name, boolean online, int ping); +} diff --git a/api/src/main/java/net/md_5/bungee/api/config/ConfigurationAdapter.java b/api/src/main/java/net/md_5/bungee/api/config/ConfigurationAdapter.java new file mode 100644 index 00000000..54f35220 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/config/ConfigurationAdapter.java @@ -0,0 +1,61 @@ +package net.md_5.bungee.api.config; + +import java.util.Collection; +import java.util.Map; + +/** + * This class allows plugins to set their own configuration adapter to load + * settings from a different place. + */ +public interface ConfigurationAdapter +{ + + /** + * Gets an integer from the specified path. + * + * @param path the path to retrieve the integer from + * @param def the default value + * @return the retrieved integer + */ + public int getInt(String path, int def); + + /** + * Gets a string from the specified path. + * + * @param path the path to retrieve the string from. + * @param def the default value + * @return the retrieved string + */ + public String getString(String path, String def); + + /** + * Get the configuration all servers which may be accessible via the proxy. + * + * @return all accessible servers, keyed by name + */ + public Map getServers(); + + /** + * Get information about all hosts to bind the proxy to. + * + * @return a list of all hosts to bind to + */ + public Collection getListeners(); + + /** + * Get all groups this player is in. + * + * @param player the player to check + * @return all the player's groups. + */ + public Collection getGroups(String player); + + /** + * Get all permission corresponding to the specified group. The result of + * this method may or may not be cached, depending on the implementation. + * + * @param group the group to check + * @return all true permissions for this group + */ + public Collection getPermissions(String group); +} diff --git a/api/src/main/java/net/md_5/bungee/api/config/ListenerInfo.java b/api/src/main/java/net/md_5/bungee/api/config/ListenerInfo.java new file mode 100644 index 00000000..7b0f0997 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/config/ListenerInfo.java @@ -0,0 +1,41 @@ +package net.md_5.bungee.api.config; + +import java.net.InetSocketAddress; +import java.util.Map; +import lombok.Data; + +/** + * Class representing the configuration of a server listener. Used for allowing + * multiple listeners on different ports. + */ +@Data +public class ListenerInfo +{ + + /** + * Host to bind to. + */ + private final InetSocketAddress host; + /** + * Displayed MOTD. + */ + private final String motd; + /** + * Max amount of slots displayed on the ping page. + */ + private final int maxPlayers; + /** + * Name of the server which users will be taken to by default. + */ + private final String defaultServer; + /** + * Whether reconnect locations will be used, or else the user is simply + * transferred to the default server on connect. + */ + private final boolean forceDefault; + /** + * A list of host to server name mappings which will force a user to be + * transferred depending on the host they connect to. + */ + private final Map forcedHosts; +} diff --git a/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java b/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java new file mode 100644 index 00000000..4ed5ddfa --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java @@ -0,0 +1,65 @@ +package net.md_5.bungee.api.config; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Synchronized; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +/** + * Class used to represent a server to connect to. + */ +@Data +@AllArgsConstructor +public class ServerInfo +{ + + /** + * Name this server displays as. + */ + private final String name; + /** + * Connectable address of this server. + */ + private final InetSocketAddress address; + /** + * Players connected to a server defined by these properties. + */ + private final Collection players = new ArrayList<>(); + + /** + * Add a player to the internal set of this server. + * + * @param player the player to add + */ + @Synchronized("players") + public void addPlayer(ProxiedPlayer player) + { + players.add(player); + } + + /** + * Remove a player form the internal set of this server. + * + * @param player the player to remove + */ + @Synchronized("players") + public void removePlayer(ProxiedPlayer player) + { + players.remove(player); + } + + /** + * Get the set of all players on this server. + * + * @return an unmodifiable collection of all players on this server + */ + @Synchronized("players") + public Collection getPlayers() + { + return Collections.unmodifiableCollection(players); + } +} diff --git a/api/src/main/java/net/md_5/bungee/api/connection/ConnectedPlayer.java b/api/src/main/java/net/md_5/bungee/api/connection/ConnectedPlayer.java new file mode 100644 index 00000000..cf09a65a --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/connection/ConnectedPlayer.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.connection; + +/** + * Represents a player physically connected to the world hosted on this server. + */ +public interface ConnectedPlayer extends ProxiedPlayer +{ +} diff --git a/api/src/main/java/net/md_5/bungee/api/connection/Connection.java b/api/src/main/java/net/md_5/bungee/api/connection/Connection.java new file mode 100644 index 00000000..3962b9a9 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/connection/Connection.java @@ -0,0 +1,19 @@ +package net.md_5.bungee.api.connection; + +import java.net.InetSocketAddress; + +/** + * A proxy connection is defined as a connection directly connected to a socket. + * It should expose information about the remote peer, however not be specific + * to a type of connection, whether server or player. + */ +public interface Connection +{ + + /** + * Gets the remote address of this connection. + * + * @return the remote address + */ + public InetSocketAddress getAddress(); +} diff --git a/api/src/main/java/net/md_5/bungee/api/connection/PendingConnection.java b/api/src/main/java/net/md_5/bungee/api/connection/PendingConnection.java new file mode 100644 index 00000000..4c952f37 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/connection/PendingConnection.java @@ -0,0 +1,47 @@ +package net.md_5.bungee.api.connection; + +import java.net.InetSocketAddress; +import net.md_5.bungee.api.config.ListenerInfo; + +/** + * Represents a user attempting to log into the proxy. + */ +public interface PendingConnection extends Connection +{ + + /** + * Get the requested username. + * + * @return the requested username, or null if not set + */ + public String getName(); + + /** + * Get the numerical client version of the player attempting to log in. + * + * @return the protocol version of the remote client + */ + public byte getVersion(); + + /** + * Get the requested virtual host that the client tried to connect to. + * + * @return request virtual host or null if invalid / not specified. + */ + public InetSocketAddress getVirtualHost(); + + /** + * Completely kick this user from the proxy and all of its child + * connections. + * + * @param reason the disconnect reason displayed to the player + */ + public void disconnect(String reason); + + /** + * Get the listener that accepted this connection. + * + * @return the accepting listener + */ + public ListenerInfo getListener(); +} diff --git a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java new file mode 100644 index 00000000..0cc70ab0 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java @@ -0,0 +1,72 @@ +package net.md_5.bungee.api.connection; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.config.ServerInfo; + +/** + * Represents a player who's connection is being connected to somewhere else, + * whether it be a remote or embedded server. + */ +public interface ProxiedPlayer extends Connection, CommandSender +{ + + /** + * Gets this player's display name. + * + * @return the players current display name + */ + public String getDisplayName(); + + /** + * Sets this players display name to be used as their nametag and tab list + * name. + * + * @param name the name to set + */ + public void setDisplayName(String name); + + /** + * Connects / transfers this user to the specified connection, gracefully + * closing the current one. Depending on the implementation, this method + * might return before the user has been connected. + * + * @param target the new server to connect to + */ + public void connect(ServerInfo target); + + /** + * Gets the server this player is connected to. + * + * @return the server this player is connected to + */ + public Server getServer(); + + /** + * Gets the ping time between the proxy and this connection. + * + * @return the current ping time + */ + public int getPing(); + + /** + * Disconnect (remove) this player from the proxy with the specified reason. + * + * @param reason the reason displayed to the player + */ + public void disconnect(String reason); + + /** + * Send a plugin message to this player. + * + * @param channel the channel to send this data via + * @param data the data to send + */ + public void sendData(String channel, byte[] data); + + /** + * Get the pending connection that belongs to this player. + * + * @return the pending connection that this player used + */ + public PendingConnection getPendingConnection(); +} diff --git a/api/src/main/java/net/md_5/bungee/api/connection/Server.java b/api/src/main/java/net/md_5/bungee/api/connection/Server.java new file mode 100644 index 00000000..caa8dd45 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/connection/Server.java @@ -0,0 +1,34 @@ +package net.md_5.bungee.api.connection; + +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.ServerPing; + +/** + * Represents a destination which this proxy might connect to. + */ +public interface Server extends Connection +{ + + /** + * Returns the basic information about this server. + * + * @return the {@link ServerInfo} for this server + */ + public ServerInfo getInfo(); + + /** + * Send data by any available means to this server. + * + * @param channel the channel to send this data via + * @param data the data to send + */ + public abstract void sendData(String channel, byte[] data); + + /** + * Asynchronously gets the current player count on this server. + * + * @param callback the callback to call when the count has been retrieved. + */ + public abstract void ping(Callback callback); +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/ChatEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ChatEvent.java new file mode 100644 index 00000000..d0285329 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/ChatEvent.java @@ -0,0 +1,33 @@ +package net.md_5.bungee.api.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.plugin.Cancellable; + +/** + * Event called when a player sends a message to a server, or a server sends a + * message to a player. + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ChatEvent extends TargetedEvent implements Cancellable +{ + + /** + * Cancelled state. + */ + private boolean cancelled; + /** + * Text contained in this chat. + */ + private String message; + + public ChatEvent(Connection sender, Connection receiver, String message) + { + super(sender, receiver); + this.message = message; + } +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/LoginEvent.java b/api/src/main/java/net/md_5/bungee/api/event/LoginEvent.java new file mode 100644 index 00000000..2da25457 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/LoginEvent.java @@ -0,0 +1,31 @@ +package net.md_5.bungee.api.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.plugin.Cancellable; +import net.md_5.bungee.api.plugin.Event; + +/** + * Event called to represent a player logging in. + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class LoginEvent extends Event implements Cancellable +{ + + /** + * Cancelled state. + */ + private boolean cancelled; + /** + * Message to use when kicking if this event is canceled. + */ + private String cancelReason; + /** + * Connection attempting to login. + */ + private final PendingConnection connection; +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/PluginMessageEvent.java b/api/src/main/java/net/md_5/bungee/api/event/PluginMessageEvent.java new file mode 100644 index 00000000..7ac4cf86 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/PluginMessageEvent.java @@ -0,0 +1,37 @@ +package net.md_5.bungee.api.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.plugin.Cancellable; + +/** + * Event called when a plugin message is sent to the client or server. + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class PluginMessageEvent extends TargetedEvent implements Cancellable +{ + + /** + * Cancelled state. + */ + private boolean cancelled; + /** + * Tag specified for this plugin message. + */ + private final String tag; + /** + * Data contained in this plugin message. + */ + private final byte[] data; + + public PluginMessageEvent(Connection sender, Connection receiver, String tag, byte[] data) + { + super(sender, receiver); + this.tag = tag; + this.data = data; + } +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java new file mode 100644 index 00000000..e48e59cf --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java @@ -0,0 +1,32 @@ +package net.md_5.bungee.api.event; + +import java.net.InetSocketAddress; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.plugin.Event; + +/** + * Called when the proxy is pinged with packet 0xFE from the server list. + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ProxyPingEvent extends Event +{ + + /** + * The address of the user pinging. + */ + private final InetSocketAddress remoteAddress; + /** + * The data corresponding to the server which received this ping. + */ + private final ListenerInfo server; + /** + * The data to respond with. + */ + private ServerPing response; +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java new file mode 100644 index 00000000..2637bc32 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java @@ -0,0 +1,26 @@ +package net.md_5.bungee.api.event; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Event; + +@Data +@AllArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ServerConnectEvent extends Event +{ + + /** + * Player connecting to a new server. + */ + private final ProxiedPlayer player; + /** + * Server the player will be connected to. + */ + private ServerInfo target; +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/ServerConnectedEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectedEvent.java new file mode 100644 index 00000000..e8370b88 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectedEvent.java @@ -0,0 +1,30 @@ +package net.md_5.bungee.api.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.plugin.Event; + +/** + * Not to be confused with {@link ServerConnectEvent}, this event is called once + * a connection to a server is fully operational, and is about to hand over + * control of the session to the player. It is useful if you wish to send + * information to the server before the player logs in. + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ServerConnectedEvent extends Event +{ + + /** + * Player whom the server is for. + */ + private final ProxiedPlayer player; + /** + * The server itself. + */ + private final Server server; +} diff --git a/api/src/main/java/net/md_5/bungee/api/event/TargetedEvent.java b/api/src/main/java/net/md_5/bungee/api/event/TargetedEvent.java new file mode 100644 index 00000000..c64105ab --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/TargetedEvent.java @@ -0,0 +1,29 @@ +package net.md_5.bungee.api.event; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.plugin.Event; + +/** + * An event which occurs in the communication between two nodes. It is not + * recommended to call {@link #setSender(net.md_5.bungee.api.Connection)} or + * {@link #setReceiver(net.md_5.bungee.api.Connection)} and the results of doing + * so are undefined. + */ +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public abstract class TargetedEvent extends Event +{ + + /** + * Creator of the action. + */ + private Connection sender; + /** + * Receiver of the action. + */ + private Connection receiver; +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Cancellable.java b/api/src/main/java/net/md_5/bungee/api/plugin/Cancellable.java new file mode 100644 index 00000000..2b04a2b5 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Cancellable.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.api.plugin; + +/** + * Events that implement this indicate that they may be cancelled and thus + * prevented from happening. + */ +public interface Cancellable +{ + + /** + * Get whether or not this event is cancelled. + * + * @return the cancelled state of this event + */ + public boolean isCancelled(); + + /** + * Sets the cancelled state of this event. + * + * @param cancel the state to set + */ + public void setCancelled(boolean cancel); +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Command.java b/api/src/main/java/net/md_5/bungee/api/plugin/Command.java new file mode 100644 index 00000000..9f5d088f --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Command.java @@ -0,0 +1,52 @@ +package net.md_5.bungee.api.plugin; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.api.CommandSender; + +/** + * A command that can be executed by a {@link CommandSender}. + */ +@Data +@RequiredArgsConstructor(access = AccessLevel.NONE) +public abstract class Command +{ + + private final String name; + private final String permission; + private final String[] aliases; + + /** + * Construct a new command with no permissions or aliases. + * + * @param name the name of this command + */ + public Command(String name) + { + this(name, null); + } + + /** + * Construct a new command. + * + * @param name primary name of this command + * @param permission the permission node required to execute this command, + * null or empty string allows it to be executed by everyone + * @param aliases aliases which map back to this command + */ + public Command(String name, String permission, String... aliases) + { + this.name = name; + this.permission = permission; + this.aliases = aliases; + } + + /** + * Execute this command with the specified sender and arguments. + * + * @param sender the executor of this command + * @param args arguments used to invoke this command + */ + public abstract void execute(CommandSender sender, String[] args); +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Event.java b/api/src/main/java/net/md_5/bungee/api/plugin/Event.java new file mode 100644 index 00000000..c164343d --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Event.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.plugin; + +/** + * Dummy class which all callable events must extend. + */ +public abstract class Event +{ +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java b/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java new file mode 100644 index 00000000..31ed4eea --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.plugin; + +/** + * Dummy interface which all event subscribers and listeners must implement. + */ +public interface Listener +{ +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java b/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java new file mode 100644 index 00000000..ce315855 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java @@ -0,0 +1,48 @@ +package net.md_5.bungee.api.plugin; + +import lombok.Getter; +import net.md_5.bungee.api.config.ConfigurationAdapter; + +/** + * Represents any Plugin that may be loaded at runtime to enhance existing + * functionality. + */ +public class Plugin +{ + + @Getter + private PluginDescription description; + + /** + * Called when the plugin has just been loaded. Most of the proxy will not + * be initialized, so only use it for registering + * {@link ConfigurationAdapter}'s and other predefined behavior. + */ + public void onLoad() + { + } + + /** + * Called when this plugin is enabled. + */ + public void onEnable() + { + } + + /** + * Called when this plugin is disabled. + */ + public void onDisable() + { + } + + /** + * Called by the loader to initialize the fields in this plugin. + * + * @param description the description that describes this plugin + */ + final void init(PluginDescription description) + { + this.description = description; + } +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java new file mode 100644 index 00000000..0739cec8 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java @@ -0,0 +1,28 @@ +package net.md_5.bungee.api.plugin; + +import lombok.Data; + +/** + * POJO representing the plugin.yml file. + */ +@Data +public class PluginDescription +{ + + /** + * Friendly name of the plugin. + */ + private final String name; + /** + * Plugin main class. Needs to extend {@link Plugin}. + */ + private final String main; + /** + * Plugin version. + */ + private final String version; + /** + * Plugin author. + */ + private final String author; +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java new file mode 100644 index 00000000..684f4141 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java @@ -0,0 +1,218 @@ +package net.md_5.bungee.api.plugin; + +import com.google.common.base.Preconditions; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.regex.Pattern; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import org.yaml.snakeyaml.Yaml; + +/** + * Class to manage bridging between plugin duties and implementation duties, for + * example event handling and plugin management. + */ +public class PluginManager +{ + + private static final Pattern argsSplit = Pattern.compile(" "); + /*========================================================================*/ + private final Yaml yaml = new Yaml(); + private final EventBus eventBus = new EventBus(); + private final Map plugins = new HashMap<>(); + private final Map commandMap = new HashMap<>(); + + /** + * Register a command so that it may be executed. + * + * @param command the command to register + */ + public void registerCommand(Command command) + { + commandMap.put(command.getName(), command); + for (String alias : command.getAliases()) + { + commandMap.put(alias, command); + } + } + + /** + * Unregister a command so it will no longer be executed. + * + * @param command the command to unregister + */ + public void unregisterCommand(Command command) + { + commandMap.values().remove(command); + } + + /** + * Execute a command if it is registered, else return false. + * + * @param sender the sender executing the command + * @param commandLine the complete command line including command name and + * arguments + * @return whether the command was handled + */ + public boolean dispatchCommand(CommandSender sender, String commandLine) + { + String[] split = argsSplit.split(commandLine); + Command command = commandMap.get(split[0]); + if (command == null) + { + return false; + } + + String permission = command.getPermission(); + if (permission != null && !permission.isEmpty() && !sender.hasPermission(permission)) + { + sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!"); + return true; + } + + String[] args = Arrays.copyOfRange(split, 1, split.length); + try + { + command.execute(sender, args); + } catch (Exception ex) + { + sender.sendMessage(ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details."); + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Error in dispatching command", ex); + } + return true; + } + + /** + * Returns the {@link Plugin} objects corresponding to all loaded plugins. + * + * @return the set of loaded plugins + */ + public Collection getPlugins() + { + return plugins.values(); + } + + /** + * Returns a loaded plugin identified by the specified name. + * + * @param name of the plugin to retrieve + * @return the retrieved plugin or null if not loaded + */ + public Plugin getPlugin(String name) + { + return plugins.get(name); + } + + /** + * Enable all plugins by calling the {@link Plugin#onEnable()} method. + */ + public void enablePlugins() + { + for (Map.Entry plugin : plugins.entrySet()) + { + try + { + plugin.getValue().onEnable(); + } catch (Exception ex) + { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Exception encountered when loading plugin: " + plugin.getKey(), ex); + } + } + } + + /** + * Load a plugin from the specified file. This file must be in jar format. + * This will not enable plugins, {@link #enablePlugins()} must be called. + * + * @param file the file to load from + * @throws Exception Any exceptions encountered when loading a plugin from + * this file. + */ + public void loadPlugin(File file) throws Exception + { + Preconditions.checkNotNull(file, "file"); + Preconditions.checkArgument(file.isFile(), "Must load from file"); + + try (JarFile jar = new JarFile(file)) + { + JarEntry pdf = jar.getJarEntry("plugin.yml"); + try (InputStream in = jar.getInputStream(pdf)) + { + PluginDescription desc = yaml.loadAs(in, PluginDescription.class); + URLClassLoader loader = new URLClassLoader(new URL[] + { + file.toURI().toURL() + }); + Class main = loader.loadClass(desc.getMain()); + Plugin plugin = (Plugin) main.getDeclaredConstructor().newInstance(); + + plugin.init(desc); + plugins.put(pdf.getName(), plugin); + plugin.onLoad(); + } + } + } + + /** + * Load all plugins from the specified folder. + * + * @param folder the folder to search for plugins in + */ + public void loadPlugins(File folder) + { + Preconditions.checkNotNull(folder, "folder"); + Preconditions.checkArgument(folder.isDirectory(), "Must load from a directory"); + + for (File file : folder.listFiles()) + { + if (file.isFile() && file.getName().endsWith(".jar")) + { + try + { + loadPlugin(file); + } catch (Exception ex) + { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not load plugin from file " + file, ex); + } + } + } + } + + /** + * Dispatch an event to all subscribed listeners and return the event once + * it has been handled by these listeners. + * + * @param the type bounds, must be a class which extends event + * @param event the event to call + * @return the called event + */ + public T callEvent(T event) + { + eventBus.post(event); + return event; + } + + /** + * Register a {@link Listener} for receiving called events. Methods in this + * Object which wish to receive events must be annotated with the + * {@link Subscribe} annotation. + * + * @param listener the listener to register events for + */ + public void registerListener(Listener listener) + { + eventBus.register(listener); + } +} diff --git a/pom.xml b/pom.xml index bcd47e5a..ca0dd096 100644 --- a/pom.xml +++ b/pom.xml @@ -10,12 +10,12 @@ net.md-5 - bungeecord - 1.0-SNAPSHOT - jar + bungeecord-parent + 1.4.7-SNAPSHOT + pom BungeeCord - Proxy component of the Elastic Portal Suite + Parent project for all BungeeCord modules. https://github.com/ElasticPortalSuite/BungeeCord 2012 @@ -36,6 +36,11 @@ + + api + proxy + + scm:git:git@github.com:ElasticPortalSuite/BungeeCord.git scm:git:git@github.com:ElasticPortalSuite/BungeeCord.git @@ -53,35 +58,9 @@ UTF-8 unknown - net.md_5.bungee.BungeeCord - - com.google.code.findbugs - jsr305 - 2.0.1 - - - com.google.guava - guava - 13.0.1 - - - net.md-5 - mendax - 1.4.6-SNAPSHOT - - - org.bouncycastle - bcprov-ext-jdk15on - 1.47 - - - org.yaml - snakeyaml - 1.11 - org.projectlombok lombok @@ -91,7 +70,6 @@ - ${project.name} com.lukegb.mojo @@ -119,78 +97,6 @@ 1.7 - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - ${main.class} - ${describe} - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.0 - - - package - - shade - - - - - - - *:* - - **/*.java - **/*.properties - **/*.SF - **/*.DSA - - - - - - - com.github.wvengen - proguard-maven-plugin - 2.0.6 - - - package - - proguard - - - - - true - false - - ${java.home}/lib/rt.jar - ${java.home}/lib/jce.jar - - false - - - - - - - - net.sf.proguard - proguard-base - 4.8 - - - diff --git a/proxy/pom.xml b/proxy/pom.xml new file mode 100644 index 00000000..3a3d0f5a --- /dev/null +++ b/proxy/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + + net.md-5 + bungeecord-parent + 1.4.7-SNAPSHOT + ../pom.xml + + + net.md-5 + bungeecord-proxy + 1.4.7-SNAPSHOT + jar + + BungeeCord-Proxy + Proxy component of the Elastic Portal Suite + + + + net.md-5 + mendax + 1.4.6-SNAPSHOT + + + net.md-5 + bungeecord-api + ${project.version} + + + org.bouncycastle + bcprov-jdk15on + 1.47 + + + + + BungeeCord + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + net.md_5.bungee.BungeeCord + ${describe} + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.0 + + + package + + shade + + + + + + + *:* + + **/*.java + **/*.properties + **/*.SF + **/*.DSA + + + + true + + + + + diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java new file mode 100644 index 00000000..bc0f83ec --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -0,0 +1,325 @@ +package net.md_5.bungee; + +import net.md_5.bungee.config.Configuration; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import lombok.Getter; +import lombok.Setter; +import lombok.Synchronized; +import static net.md_5.bungee.Logger.$; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ReconnectHandler; +import net.md_5.bungee.api.TabListHandler; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginManager; +import net.md_5.bungee.command.*; +import net.md_5.bungee.config.YamlConfig; +import net.md_5.bungee.packet.DefinedPacket; +import net.md_5.bungee.packet.PacketFAPluginMessage; + +/** + * Main BungeeCord proxy class. + */ +public class BungeeCord extends ProxyServer +{ + + /** + * Server protocol version. + */ + public static final int PROTOCOL_VERSION = 51; + /** + * Server game version. + */ + public static final String GAME_VERSION = "1.4.6"; + /** + * Current operation state. + */ + public volatile boolean isRunning; + /** + * Configuration. + */ + public final Configuration config = new Configuration(); + /** + * Thread pool. + */ + public final ExecutorService threadPool = Executors.newCachedThreadPool(); + /** + * locations.yml save thread. + */ + private final Timer saveThread = new Timer("Reconnect Saver"); + /** + * Server socket listener. + */ + private Collection listeners = new HashSet<>(); + /** + * Fully qualified connections. + */ + public Map connections = new ConcurrentHashMap<>(); + /** + * Tab list handler + */ + @Getter + @Setter + public TabListHandler tabListHandler; + /** + * Plugin manager. + */ + @Getter + public final PluginManager pluginManager = new PluginManager(); + @Getter + @Setter + private ReconnectHandler reconnectHandler; + @Getter + @Setter + private ConfigurationAdapter configurationAdapter = new YamlConfig(); + private final Collection pluginChannels = new HashSet<>(); + + + { + getPluginManager().registerCommand(new CommandReload()); + getPluginManager().registerCommand(new CommandEnd()); + getPluginManager().registerCommand(new CommandList()); + getPluginManager().registerCommand(new CommandServer()); + getPluginManager().registerCommand(new CommandIP()); + getPluginManager().registerCommand(new CommandAlert()); + getPluginManager().registerCommand(new CommandBungee()); + + registerChannel("BungeeCord::Disconnect"); + registerChannel("BungeeCord::Connect"); + registerChannel("BungeeCord::Forward"); + } + + public static BungeeCord getInstance() + { + return (BungeeCord) ProxyServer.getInstance(); + } + + /** + * Starts a new instance of BungeeCord. + * + * @param args command line arguments, currently none are used + * @throws IOException when the server cannot be started + */ + public static void main(String[] args) throws IOException + { + BungeeCord bungee = new BungeeCord(); + ProxyServer.setInstance(bungee); + $().info("Enabled BungeeCord version " + bungee.getVersion()); + bungee.start(); + + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + while (bungee.isRunning) + { + String line = br.readLine(); + if (line != null) + { + boolean handled = getInstance().getPluginManager().dispatchCommand(ConsoleCommandSender.getInstance(), line); + if (!handled) + { + System.err.println("Command not found"); + } + } + } + } + + /** + * Start this proxy instance by loading the configuration, plugins and + * starting the connect thread. + * + * @throws IOException + */ + public void start() throws IOException + { + config.load(); + reconnectHandler = new YamlReconnectHandler(); + isRunning = true; + + File plugins = new File("plugins"); + plugins.mkdir(); + pluginManager.loadPlugins(plugins); + + for (ListenerInfo info : config.getListeners()) + { + $().info("Listening on " + info.getHost()); + ListenThread listener = new ListenThread(info); + listener.start(); + listeners.add(listener); + } + + saveThread.scheduleAtFixedRate(new TimerTask() + { + @Override + public void run() + { + getReconnectHandler().save(); + } + }, 0, TimeUnit.MINUTES.toMillis(5)); + + new Metrics().start(); + } + + @Override + public void stop() + { + this.isRunning = false; + $().info("Disabling plugins"); + for (Plugin plugin : pluginManager.getPlugins()) + { + plugin.onDisable(); + } + + for (ListenThread listener : listeners) + { + $().log(Level.INFO, "Closing listen thread {0}", listener.socket); + try + { + listener.socket.close(); + listener.join(); + } catch (InterruptedException | IOException ex) + { + $().severe("Could not close listen thread"); + } + } + + $().info("Closing pending connections"); + threadPool.shutdown(); + + $().info("Disconnecting " + connections.size() + " connections"); + for (UserConnection user : connections.values()) + { + user.disconnect("Proxy restarting, brb."); + } + + $().info("Saving reconnect locations"); + reconnectHandler.save(); + saveThread.cancel(); + + $().info("Thank you and goodbye"); + System.exit(0); + } + + /** + * Miscellaneous method to set options on a socket based on those in the + * configuration. + * + * @param socket to set the options on + * @throws IOException when the underlying set methods thrown an exception + */ + public void setSocketOptions(Socket socket) throws IOException + { + socket.setSoTimeout(config.getTimeout()); + socket.setTrafficClass(0x18); + socket.setTcpNoDelay(true); + } + + /** + * Broadcasts a packet to all clients that is connected to this instance. + * + * @param packet the packet to send + */ + public void broadcast(DefinedPacket packet) + { + for (UserConnection con : connections.values()) + { + con.packetQueue.add(packet); + } + } + + @Override + public String getName() + { + return "BungeeCord"; + } + + @Override + public String getVersion() + { + return (BungeeCord.class.getPackage().getImplementationVersion() == null) ? "unknown" : BungeeCord.class.getPackage().getImplementationVersion(); + } + + @Override + public Logger getLogger() + { + return $(); + } + + @Override + @SuppressWarnings("unchecked") // TODO: Abstract more + public Collection getPlayers() + { + return (Collection) connections.values(); + } + + @Override + public ProxiedPlayer getPlayer(String name) + { + return connections.get(name); + } + + @Override + public Server getServer(String name) + { + Collection users = getServers().get(name).getPlayers(); + return (users != null && !users.isEmpty()) ? users.iterator().next().getServer() : null; + } + + @Override + public Map getServers() + { + return config.getServers(); + } + + @Override + @Synchronized("pluginChannels") + public void registerChannel(String channel) + { + pluginChannels.add(channel); + } + + @Override + @Synchronized("pluginChannels") + public void unregisterChannel(String channel) + { + pluginChannels.remove(channel); + } + + @Override + @Synchronized("pluginChannels") + public Collection getChannels() + { + return Collections.unmodifiableCollection(pluginChannels); + } + + public PacketFAPluginMessage registerChannels() + { + StringBuilder sb = new StringBuilder(); + for (String s : getChannels()) + { + sb.append(s); + sb.append('\00'); + } + byte[] payload = sb.substring(0, sb.length() - 1).getBytes(); + return new PacketFAPluginMessage("REGISTER", payload); + } +} diff --git a/src/main/java/net/md_5/bungee/EncryptionUtil.java b/proxy/src/main/java/net/md_5/bungee/EncryptionUtil.java similarity index 100% rename from src/main/java/net/md_5/bungee/EncryptionUtil.java rename to proxy/src/main/java/net/md_5/bungee/EncryptionUtil.java diff --git a/src/main/java/net/md_5/bungee/EntityMap.java b/proxy/src/main/java/net/md_5/bungee/EntityMap.java similarity index 100% rename from src/main/java/net/md_5/bungee/EntityMap.java rename to proxy/src/main/java/net/md_5/bungee/EntityMap.java diff --git a/src/main/java/net/md_5/bungee/GenericConnection.java b/proxy/src/main/java/net/md_5/bungee/GenericConnection.java similarity index 86% rename from src/main/java/net/md_5/bungee/GenericConnection.java rename to proxy/src/main/java/net/md_5/bungee/GenericConnection.java index b06fa2d2..a4e89301 100644 --- a/src/main/java/net/md_5/bungee/GenericConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/GenericConnection.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.RequiredArgsConstructor; import static net.md_5.bungee.Logger.$; import net.md_5.bungee.packet.PacketFFKick; @@ -20,8 +21,10 @@ public class GenericConnection protected final Socket socket; protected final PacketInputStream in; protected final OutputStream out; - public String username; - public String tabListName; + @Getter + public String name; + @Getter + public String displayName; /** * Close the socket with the specified reason. @@ -55,6 +58,6 @@ public class GenericConnection public void log(String message) { - $().info(socket.getInetAddress() + ((username == null) ? " " : " [" + username + "] ") + message); + $().info(socket.getInetAddress() + ((name == null) ? " " : " [" + name + "] ") + message); } } diff --git a/src/main/java/net/md_5/bungee/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/InitialHandler.java similarity index 59% rename from src/main/java/net/md_5/bungee/InitialHandler.java rename to proxy/src/main/java/net/md_5/bungee/InitialHandler.java index d27d4c7d..542e73a1 100644 --- a/src/main/java/net/md_5/bungee/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/InitialHandler.java @@ -1,31 +1,43 @@ package net.md_5.bungee; -import java.io.BufferedOutputStream; +import net.md_5.bungee.config.Configuration; import java.io.IOException; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; import java.util.List; import javax.crypto.SecretKey; +import lombok.Getter; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.packet.Packet2Handshake; import net.md_5.bungee.packet.PacketFCEncryptionResponse; import net.md_5.bungee.packet.PacketFDEncryptionRequest; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketInputStream; -import net.md_5.bungee.plugin.LoginEvent; import org.bouncycastle.crypto.io.CipherInputStream; import org.bouncycastle.crypto.io.CipherOutputStream; -public class InitialHandler implements Runnable +public class InitialHandler implements Runnable, PendingConnection { private final Socket socket; + @Getter + private final ListenerInfo listener; private PacketInputStream in; private OutputStream out; + private Packet2Handshake handshake; - public InitialHandler(Socket socket) throws IOException + public InitialHandler(Socket socket, ListenerInfo info) throws IOException { this.socket = socket; + this.listener = info; in = new PacketInputStream(socket.getInputStream()); out = socket.getOutputStream(); } @@ -40,15 +52,7 @@ public class InitialHandler implements Runnable switch (id) { case 0x02: - Packet2Handshake handshake = new Packet2Handshake(packet); - // fire connect event - LoginEvent event = new LoginEvent(handshake.username, socket.getInetAddress(), handshake.host); - BungeeCord.instance.pluginManager.onHandshake(event); - if (event.isCancelled()) - { - throw new KickException(event.getCancelReason()); - } - + handshake = new Packet2Handshake(packet); PacketFDEncryptionRequest request = EncryptionUtil.encryptRequest(); out.write(request.getPacket()); PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(in.readPacket()); @@ -60,13 +64,14 @@ public class InitialHandler implements Runnable } // Check for multiple connections - if (BungeeCord.instance.connections.containsKey(handshake.username)) + ProxiedPlayer old = ProxyServer.getInstance().getPlayer(handshake.username); + if (old != null) { - throw new KickException("You are already connected to the server"); + old.disconnect("You are already connected to the server"); } - // fire post auth event - BungeeCord.instance.pluginManager.onLogin(event); + // fire login event + LoginEvent event = new LoginEvent(this); if (event.isCancelled()) { throw new KickException(event.getCancelReason()); @@ -82,9 +87,10 @@ public class InitialHandler implements Runnable customPackets.add(custom); } - UserConnection userCon = new UserConnection(socket, in, out, handshake, customPackets); - String server = (BungeeCord.instance.config.forceDefaultServer) ? BungeeCord.instance.config.defaultServerName : BungeeCord.instance.config.getServer(handshake.username, handshake.host); - userCon.connect(server); + UserConnection userCon = new UserConnection(socket, this, in, out, handshake, customPackets); + String server = ProxyServer.getInstance().getReconnectHandler().getServer(userCon); + ServerInfo s = BungeeCord.getInstance().config.getServers().get(server); + userCon.connect(s); break; case 0xFE: socket.setSoTimeout(100); @@ -96,14 +102,14 @@ public class InitialHandler implements Runnable } catch (IOException ex) { } - Configuration conf = BungeeCord.instance.config; + Configuration conf = BungeeCord.getInstance().config; String ping = (newPing) ? ChatColor.COLOR_CHAR + "1" + "\00" + BungeeCord.PROTOCOL_VERSION + "\00" + BungeeCord.GAME_VERSION - + "\00" + conf.motd - + "\00" + BungeeCord.instance.connections.size() - + "\00" + conf.maxPlayers - : conf.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.connections.size() + ChatColor.COLOR_CHAR + conf.maxPlayers; + + "\00" + listener.getMotd() + + "\00" + ProxyServer.getInstance().getPlayers().size() + + "\00" + listener.getMaxPlayers() + : listener.getMotd() + ChatColor.COLOR_CHAR + ProxyServer.getInstance().getPlayers().size() + ChatColor.COLOR_CHAR + listener.getMaxPlayers(); throw new KickException(ping); default: if (id == 0xFA) @@ -116,18 +122,20 @@ public class InitialHandler implements Runnable } } catch (KickException ex) { - kick(ex.getMessage()); + disconnect(ex.getMessage()); } catch (Exception ex) { - kick("[Proxy Error] " + Util.exception(ex)); + disconnect("[Proxy Error] " + Util.exception(ex)); + ex.printStackTrace(); } } - private void kick(String message) + @Override + public void disconnect(String reason) { try { - out.write(new PacketFFKick(message).getPacket()); + out.write(new PacketFFKick(reason).getPacket()); } catch (IOException ioe) { } finally @@ -141,4 +149,28 @@ public class InitialHandler implements Runnable } } } + + @Override + public String getName() + { + return (handshake == null) ? null : handshake.username; + } + + @Override + public byte getVersion() + { + return (handshake == null) ? -1 : handshake.procolVersion; + } + + @Override + public InetSocketAddress getVirtualHost() + { + return (handshake == null) ? null : new InetSocketAddress(handshake.host, handshake.port); + } + + @Override + public InetSocketAddress getAddress() + { + return (InetSocketAddress) socket.getRemoteSocketAddress(); + } } diff --git a/src/main/java/net/md_5/bungee/KickException.java b/proxy/src/main/java/net/md_5/bungee/KickException.java similarity index 100% rename from src/main/java/net/md_5/bungee/KickException.java rename to proxy/src/main/java/net/md_5/bungee/KickException.java diff --git a/src/main/java/net/md_5/bungee/ListenThread.java b/proxy/src/main/java/net/md_5/bungee/ListenThread.java similarity index 65% rename from src/main/java/net/md_5/bungee/ListenThread.java rename to proxy/src/main/java/net/md_5/bungee/ListenThread.java index e42e04ad..ab54b1ff 100644 --- a/src/main/java/net/md_5/bungee/ListenThread.java +++ b/proxy/src/main/java/net/md_5/bungee/ListenThread.java @@ -1,11 +1,11 @@ package net.md_5.bungee; import java.io.IOException; -import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import static net.md_5.bungee.Logger.$; +import net.md_5.bungee.api.config.ListenerInfo; /** * Thread to listen and dispatch incoming connections to the proxy. @@ -14,26 +14,28 @@ public class ListenThread extends Thread { public final ServerSocket socket; + private final ListenerInfo info; - public ListenThread(InetSocketAddress addr) throws IOException + public ListenThread(ListenerInfo info) throws IOException { - super("Listen Thread"); + super("Listen Thread - "+ info); + this.info = info; socket = new ServerSocket(); - socket.bind(addr); + socket.bind(info.getHost()); } @Override public void run() { - while (BungeeCord.instance.isRunning) + while (BungeeCord.getInstance().isRunning) { try { Socket client = socket.accept(); - BungeeCord.instance.setSocketOptions(client); + BungeeCord.getInstance().setSocketOptions(client); $().info(client.getInetAddress() + " has connected"); - InitialHandler handler = new InitialHandler(client); - BungeeCord.instance.threadPool.submit(handler); + InitialHandler handler = new InitialHandler(client,info); + BungeeCord.getInstance().threadPool.submit(handler); } catch (SocketException ex) { } catch (IOException ex) diff --git a/src/main/java/net/md_5/bungee/Logger.java b/proxy/src/main/java/net/md_5/bungee/Logger.java similarity index 96% rename from src/main/java/net/md_5/bungee/Logger.java rename to proxy/src/main/java/net/md_5/bungee/Logger.java index 52f4ffd9..5f570159 100644 --- a/src/main/java/net/md_5/bungee/Logger.java +++ b/proxy/src/main/java/net/md_5/bungee/Logger.java @@ -23,7 +23,7 @@ public class Logger extends java.util.logging.Logger super("RubberBand", null); try { - FileHandler handler = new FileHandler("proxy.log", BungeeCord.instance.config.logNumLines, 1, true); + FileHandler handler = new FileHandler("proxy.log", 1 << 14, 1, true); handler.setFormatter(formatter); addHandler(handler); } catch (IOException ex) diff --git a/src/main/java/net/md_5/bungee/Metrics.java b/proxy/src/main/java/net/md_5/bungee/Metrics.java similarity index 94% rename from src/main/java/net/md_5/bungee/Metrics.java rename to proxy/src/main/java/net/md_5/bungee/Metrics.java index fc5e81e5..a770a093 100644 --- a/src/main/java/net/md_5/bungee/Metrics.java +++ b/proxy/src/main/java/net/md_5/bungee/Metrics.java @@ -9,6 +9,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import static net.md_5.bungee.Logger.$; +import net.md_5.bungee.api.ProxyServer; public class Metrics extends Thread { @@ -73,10 +74,10 @@ public class Metrics extends Thread { // Construct the post data final StringBuilder data = new StringBuilder(); - data.append(encode("guid")).append('=').append(encode(BungeeCord.instance.config.statsUuid)); - encodeDataPair(data, "version", BungeeCord.instance.version); + data.append(encode("guid")).append('=').append(encode(BungeeCord.getInstance().config.getUuid())); + encodeDataPair(data, "version", ProxyServer.getInstance().getVersion()); encodeDataPair(data, "server", "0"); - encodeDataPair(data, "players", Integer.toString(BungeeCord.instance.connections.size())); + encodeDataPair(data, "players", Integer.toString(ProxyServer.getInstance().getPlayers().size())); encodeDataPair(data, "revision", String.valueOf(REVISION)); // If we're pinging, append it diff --git a/src/main/java/net/md_5/bungee/ServerConnection.java b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java similarity index 60% rename from src/main/java/net/md_5/bungee/ServerConnection.java rename to proxy/src/main/java/net/md_5/bungee/ServerConnection.java index 500971b0..54db34e3 100644 --- a/src/main/java/net/md_5/bungee/ServerConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java @@ -7,6 +7,14 @@ import java.security.PublicKey; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import javax.crypto.SecretKey; +import lombok.Getter; +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.ServerConnectedEvent; import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.Packet2Handshake; @@ -22,27 +30,28 @@ import org.bouncycastle.crypto.io.CipherOutputStream; /** * Class representing a connection from the proxy to the server; ie upstream. */ -public class ServerConnection extends GenericConnection +public class ServerConnection extends GenericConnection implements Server { - public final String name; + @Getter + private final ServerInfo info; public final Packet1Login loginPacket; public Queue packetQueue = new ConcurrentLinkedQueue<>(); - public ServerConnection(String name, Socket socket, PacketInputStream in, OutputStream out, Packet1Login loginPacket) + public ServerConnection(Socket socket, ServerInfo info, PacketInputStream in, OutputStream out, Packet1Login loginPacket) { super(socket, in, out); - this.name = name; + this.info = info; this.loginPacket = loginPacket; } - public static ServerConnection connect(UserConnection user, String name, InetSocketAddress address, Packet2Handshake handshake, boolean retry) + public static ServerConnection connect(UserConnection user, ServerInfo info, Packet2Handshake handshake, boolean retry) { try { Socket socket = new Socket(); - socket.connect(address, BungeeCord.instance.config.timeout); - BungeeCord.instance.setSocketOptions(socket); + socket.connect(info.getAddress(), BungeeCord.getInstance().config.getTimeout()); + BungeeCord.getInstance().setSocketOptions(socket); PacketInputStream in = new PacketInputStream(socket.getInputStream()); OutputStream out = socket.getOutputStream(); @@ -78,27 +87,45 @@ public class ServerConnection extends GenericConnection } Packet1Login login = new Packet1Login(loginResponse); - // Register all global plugin message channels - // TODO: Allow player-specific plugin message channels for full mod support - for (String channel : BungeeCord.instance.globalPluginChannels) - { - out.write(new PacketFAPluginMessage("REGISTER", channel.getBytes()).getPacket()); - } + ServerConnection server = new ServerConnection(socket, info, in, out, login); + ServerConnectedEvent event = new ServerConnectedEvent(user, server); + ProxyServer.getInstance().getPluginManager().callEvent(event); - return new ServerConnection(name, socket, in, out, login); + out.write(BungeeCord.getInstance().registerChannels().getPacket()); + + return server; } catch (KickException ex) { throw ex; } catch (Exception ex) { - InetSocketAddress def = BungeeCord.instance.config.getServer(null); - if (retry && !address.equals(def)) + ServerInfo def = ProxyServer.getInstance().getServers().get(user.getPendingConnection().getListener().getDefaultServer()); + if (retry && !info.equals(def)) { - return connect(user, BungeeCord.instance.config.defaultServerName, def, handshake, false); + user.sendMessage(ChatColor.RED + "Could not connect to target server, you have been moved to the default server"); + return connect(user, def, handshake, false); } else { throw new RuntimeException("Could not connect to target server " + Util.exception(ex)); } } } + + @Override + public void sendData(String channel, byte[] data) + { + packetQueue.add(new PacketFAPluginMessage(channel, data)); + } + + @Override + public void ping(Callback callback) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public InetSocketAddress getAddress() + { + return getInfo().getAddress(); + } } diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java new file mode 100644 index 00000000..32a2c154 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -0,0 +1,428 @@ +package net.md_5.bungee; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import lombok.Getter; +import lombok.Synchronized; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.ChatEvent; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.packet.*; + +public class UserConnection extends GenericConnection implements ProxiedPlayer +{ + + public final Packet2Handshake handshake; + public Queue packetQueue = new ConcurrentLinkedQueue<>(); + public List loginPackets = new ArrayList<>(); + @Getter + private final PendingConnection pendingConnection; + @Getter + private ServerConnection server; + private UpstreamBridge upBridge; + private DownstreamBridge downBridge; + // reconnect stuff + private int clientEntityId; + private int serverEntityId; + private volatile boolean reconnecting; + // ping stuff + private int trackingPingId; + private long pingTime; + @Getter + private int ping; + // Permissions + private final Collection groups = new HashSet<>(); + private final Map permissions = new HashMap<>(); + private final Object permMutex = new Object(); + + public UserConnection(Socket socket, PendingConnection pendingConnection, PacketInputStream in, OutputStream out, Packet2Handshake handshake, List loginPackets) + { + super(socket, in, out); + this.handshake = handshake; + this.pendingConnection = pendingConnection; + name = handshake.username; + displayName = handshake.username; + this.loginPackets = loginPackets; + Collection g = ProxyServer.getInstance().getConfigurationAdapter().getGroups(name); + for (String s : g) + { + addGroups(s); + } + } + + @Override + public void setDisplayName(String name) + { + ProxyServer.getInstance().getTabListHandler().onDisconnect(this); + displayName = name; + ProxyServer.getInstance().getTabListHandler().onConnect(this); + } + + @Override + public void connect(ServerInfo target) + { + if (server == null) + { + // First join + BungeeCord.getInstance().connections.put(name, this); + ProxyServer.getInstance().getTabListHandler().onConnect(this); + } + + ServerConnectEvent event = new ServerConnectEvent(this, target); + BungeeCord.getInstance().getPluginManager().callEvent(event); + target = event.getTarget(); // Update in case the event changed target + + ProxyServer.getInstance().getTabListHandler().onServerChange(this); + try + { + reconnecting = true; + + if (server != null) + { + 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()); + } + + ServerConnection newServer = ServerConnection.connect(this, target, handshake, true); + if (server == null) + { + // Once again, first connection + clientEntityId = newServer.loginPacket.entityId; + serverEntityId = newServer.loginPacket.entityId; + out.write(newServer.loginPacket.getPacket()); + out.write(BungeeCord.getInstance().registerChannels().getPacket()); + + upBridge = new UpstreamBridge(); + upBridge.start(); + } else + { + try + { + downBridge.interrupt(); + downBridge.join(); + } catch (InterruptedException ie) + { + } + + server.disconnect("Quitting"); + server.getInfo().removePlayer(this); + + Packet1Login login = newServer.loginPacket; + serverEntityId = login.entityId; + out.write(new Packet9Respawn(login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType).getPacket()); + } + + // Reconnect process has finished, lets get the player moving again + reconnecting = false; + + // Add to new + target.addPlayer(this); + + // Start the bridges and move on + server = newServer; + downBridge = new DownstreamBridge(); + downBridge.start(); + } catch (KickException ex) + { + destroySelf(ex.getMessage()); + } catch (Exception ex) + { + ex.printStackTrace(); // TODO: Remove + destroySelf("Could not connect to server - " + ex.getClass().getSimpleName()); + } + } + + private void destroySelf(String reason) + { + server.getInfo().removePlayer(this); + ProxyServer.getInstance().getPlayers().remove(this); + + disconnect(reason); + if (server != null) + { + server.disconnect("Quitting"); + ProxyServer.getInstance().getReconnectHandler().setServer(this); + } + } + + @Override + public void disconnect(String reason) + { + ProxyServer.getInstance().getTabListHandler().onDisconnect(this); + super.disconnect(reason); + } + + @Override + public void sendMessage(String message) + { + packetQueue.add(new Packet3Chat(message)); + } + + @Override + public void sendData(String channel, byte[] data) + { + server.packetQueue.add(new PacketFAPluginMessage(channel, data)); + } + + @Override + public InetSocketAddress getAddress() + { + return (InetSocketAddress) socket.getRemoteSocketAddress(); + } + + @Override + @Synchronized("permMutex") + public Collection getGroups() + { + return Collections.unmodifiableCollection(groups); + } + + @Override + @Synchronized("permMutex") + public void addGroups(String... groups) + { + for (String group : groups) + { + this.groups.add(group); + for (String permission : ProxyServer.getInstance().getConfigurationAdapter().getPermissions(group)) + { + setPermission(permission, true); + } + } + } + + @Override + @Synchronized( "permMutex") + public void removeGroups(String... groups) + { + for (String group : groups) + { + this.groups.remove(group); + for (String permission : ProxyServer.getInstance().getConfigurationAdapter().getPermissions(group)) + { + setPermission(permission, false); + } + } + } + + @Override + @Synchronized("permMutex") + public boolean hasPermission(String permission) + { + Boolean val = permissions.get(permission); + return (val == null) ? false : val; + } + + @Override + @Synchronized( "permMutex") + public void setPermission(String permission, boolean value) + { + permissions.put(permission, value); + } + + private class UpstreamBridge extends Thread + { + + public UpstreamBridge() + { + super("Upstream Bridge - " + name); + } + + @Override + public void run() + { + while (!socket.isClosed()) + { + try + { + byte[] packet = in.readPacket(); + boolean sendPacket = true; + int id = Util.getId(packet); + + switch (id) + { + case 0x00: + if (trackingPingId == new Packet0KeepAlive(packet).id) + { + int newPing = (int) (System.currentTimeMillis() - pingTime); + ProxyServer.getInstance().getTabListHandler().onPingChange(UserConnection.this, newPing); + ping = newPing; + } + break; + case 0x03: + Packet3Chat chat = new Packet3Chat(packet); + if (chat.message.startsWith("/")) + { + sendPacket = !ProxyServer.getInstance().getPluginManager().dispatchCommand(UserConnection.this, chat.message.substring(1)); + } else + { + ChatEvent chatEvent = new ChatEvent(UserConnection.this, server, chat.message); + ProxyServer.getInstance().getPluginManager().callEvent(chatEvent); + sendPacket = !chatEvent.isCancelled(); + } + break; + case 0xFA: + // Call the onPluginMessage event + PacketFAPluginMessage message = new PacketFAPluginMessage(packet); + PluginMessageEvent event = new PluginMessageEvent(UserConnection.this, server, message.tag, message.data); + ProxyServer.getInstance().getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + continue; + } + break; + } + + while (!server.packetQueue.isEmpty()) + { + DefinedPacket p = server.packetQueue.poll(); + if (p != null) + { + server.out.write(p.getPacket()); + } + } + + EntityMap.rewrite(packet, clientEntityId, serverEntityId); + if (sendPacket && !server.socket.isClosed()) + { + server.out.write(packet); + } + } catch (IOException ex) + { + destroySelf("Reached end of stream"); + } catch (Exception ex) + { + destroySelf(Util.exception(ex)); + } + } + } + } + + private class DownstreamBridge extends Thread + { + + public DownstreamBridge() + { + super("Downstream Bridge - " + name); + } + + @Override + public void run() + { + try + { + outer: + while (!reconnecting) + { + byte[] packet = server.in.readPacket(); + int id = Util.getId(packet); + + switch (id) + { + case 0x00: + trackingPingId = new Packet0KeepAlive(packet).id; + pingTime = System.currentTimeMillis(); + break; + case 0x03: + Packet3Chat chat = new Packet3Chat(packet); + ChatEvent chatEvent = new ChatEvent(server, UserConnection.this, chat.message); + ProxyServer.getInstance().getPluginManager().callEvent(chatEvent); + + if (chatEvent.isCancelled()) + { + continue; + } + break; + case 0xC9: + PacketC9PlayerListItem playerList = new PacketC9PlayerListItem(packet); + if (!ProxyServer.getInstance().getTabListHandler().onListUpdate(UserConnection.this, playerList.username, playerList.online, playerList.ping)) + { + continue; + } + break; + case 0xFA: + // Call the onPluginMessage event + PacketFAPluginMessage message = new PacketFAPluginMessage(packet); + DataInputStream in = new DataInputStream(new ByteArrayInputStream(message.data)); + PluginMessageEvent event = new PluginMessageEvent(server, UserConnection.this, message.tag, message.data); + ProxyServer.getInstance().getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + continue; + } + + switch (message.tag) + { + case "BungeeCord::Disconnect": + break outer; + case "BungeeCord::Forward": + String target = in.readUTF(); + String channel = in.readUTF(); + short len = in.readShort(); + byte[] data = new byte[len]; + in.readFully(data); + + if (target.equals("ALL")) + { + for (String s : BungeeCord.getInstance().getServers().keySet()) + { + Server server = BungeeCord.getInstance().getServer(s); + server.sendData(channel, data); + } + } else + { + Server server = BungeeCord.getInstance().getServer(target); + server.sendData(channel, data); + } + + break; + case "BungeeCord::Connect": + ServerInfo server = BungeeCord.getInstance().config.getServers().get(in.readUTF()); + if (server != null) + { + connect(server); + break outer; + } + break; + } + } + + while (!packetQueue.isEmpty()) + { + DefinedPacket p = packetQueue.poll(); + if (p != null) + { + out.write(p.getPacket()); + } + } + + EntityMap.rewrite(packet, serverEntityId, clientEntityId); + out.write(packet); + } + } catch (Exception ex) + { + destroySelf(Util.exception(ex)); + } + } + } +} diff --git a/src/main/java/net/md_5/bungee/Util.java b/proxy/src/main/java/net/md_5/bungee/Util.java similarity index 100% rename from src/main/java/net/md_5/bungee/Util.java rename to proxy/src/main/java/net/md_5/bungee/Util.java diff --git a/proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java b/proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java new file mode 100644 index 00000000..cd9e227a --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java @@ -0,0 +1,85 @@ +package net.md_5.bungee; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ReconnectHandler; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.yaml.snakeyaml.Yaml; + +public class YamlReconnectHandler implements ReconnectHandler +{ + + private final Yaml yaml = new Yaml(); + private final File file = new File("locations.yml"); + /*========================================================================*/ + private Map data; + + @SuppressWarnings("unchecked") + public YamlReconnectHandler() + { + try + { + file.createNewFile(); + try (FileReader rd = new FileReader(file)) + { + data = yaml.loadAs(rd, Map.class); + } + } catch (IOException ex) + { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not load reconnect locations", ex); + } + + if (data == null) + { + data = new ConcurrentHashMap<>(); + } else + { + data = new ConcurrentHashMap<>(data); + } + } + + @Override + public String getServer(ProxiedPlayer player) + { + ListenerInfo listener = player.getPendingConnection().getListener(); + if (listener.isForceDefault()) + { + return listener.getDefaultServer(); + } + String forced = listener.getForcedHosts().get(player.getPendingConnection().getVirtualHost().getHostName()); + String server = (forced == null) ? data.get(key(player)) : forced; + return (server != null) ? server : listener.getDefaultServer(); + } + + @Override + public void setServer(ProxiedPlayer player) + { + data.put(key(player), player.getServer().getInfo().getName()); + } + + private String key(ProxiedPlayer player) + { + InetSocketAddress host = player.getPendingConnection().getVirtualHost(); + return player.getName() + ";" + host.getHostString() + ":" + host.getPort(); + } + + @Override + public void save() + { + try (FileWriter wr = new FileWriter(file)) + { + yaml.dump(data, wr); + } catch (IOException ex) + { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not save reconnect locations", ex); + } + } +} diff --git a/src/main/java/net/md_5/bungee/command/CommandAlert.java b/proxy/src/main/java/net/md_5/bungee/command/CommandAlert.java similarity index 62% rename from src/main/java/net/md_5/bungee/command/CommandAlert.java rename to proxy/src/main/java/net/md_5/bungee/command/CommandAlert.java index 270e89c1..a584e300 100644 --- a/src/main/java/net/md_5/bungee/command/CommandAlert.java +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandAlert.java @@ -1,21 +1,22 @@ package net.md_5.bungee.command; -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.ChatColor; -import net.md_5.bungee.Permission; -import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; public class CommandAlert extends Command { + public CommandAlert() + { + super("alert", "bungee.command.alert"); + } + @Override public void execute(CommandSender sender, String[] args) { - if (getPermission(sender) != Permission.ADMIN) - { - sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!"); - return; - } if (args.length == 0) { sender.sendMessage(ChatColor.RED + "You must supply a message."); @@ -30,15 +31,15 @@ public class CommandAlert extends Command for (String s : args) { - s = s.replace("&h", ""); + s = s.substring(1, s.length()); builder.append(ChatColor.translateAlternateColorCodes('&', s)); builder.append(" "); } String message = builder.substring(0, builder.length() - 1); - for (UserConnection con : BungeeCord.instance.connections.values()) + for (ProxiedPlayer player : ProxyServer.getInstance().getPlayers()) { - con.sendMessage(message); + player.sendMessage(message); } } } diff --git a/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java b/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java new file mode 100644 index 00000000..439a9a20 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.plugin.Command; + +public class CommandBungee extends Command +{ + + public CommandBungee() + { + super("bungee"); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + sender.sendMessage(ChatColor.BLUE + "This server is running BungeeCord version " + ProxyServer.getInstance().getVersion() + " by md_5"); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/command/CommandEnd.java b/proxy/src/main/java/net/md_5/bungee/command/CommandEnd.java new file mode 100644 index 00000000..349ef805 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandEnd.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; + +/** + * Command to terminate the proxy instance. May only be used by the console. + */ +public class CommandEnd extends Command +{ + + public CommandEnd() + { + super("end", "bungeecord.command.end"); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + BungeeCord.getInstance().stop(); + } +} diff --git a/src/main/java/net/md_5/bungee/command/CommandIP.java b/proxy/src/main/java/net/md_5/bungee/command/CommandIP.java similarity index 55% rename from src/main/java/net/md_5/bungee/command/CommandIP.java rename to proxy/src/main/java/net/md_5/bungee/command/CommandIP.java index d1b75f4b..b11c7fea 100644 --- a/src/main/java/net/md_5/bungee/command/CommandIP.java +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandIP.java @@ -1,27 +1,28 @@ package net.md_5.bungee.command; -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.ChatColor; -import net.md_5.bungee.Permission; -import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; public class CommandIP extends Command { + public CommandIP() + { + super("ip", "bungeecord.command.ip"); + } + @Override public void execute(CommandSender sender, String[] args) { - if (getPermission(sender) != Permission.MODERATOR && getPermission(sender) != Permission.ADMIN) - { - sender.sendMessage(ChatColor.RED + "You do not have permission to use this command"); - return; - } if (args.length < 1) { sender.sendMessage(ChatColor.RED + "Please follow this command by a user name"); return; } - UserConnection user = BungeeCord.instance.connections.get(args[0]); + ProxiedPlayer user = ProxyServer.getInstance().getPlayer(args[0]); if (user == null) { sender.sendMessage(ChatColor.RED + "That user is not online"); diff --git a/src/main/java/net/md_5/bungee/command/CommandList.java b/proxy/src/main/java/net/md_5/bungee/command/CommandList.java similarity index 56% rename from src/main/java/net/md_5/bungee/command/CommandList.java rename to proxy/src/main/java/net/md_5/bungee/command/CommandList.java index 42584a4d..f836b26b 100644 --- a/src/main/java/net/md_5/bungee/command/CommandList.java +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandList.java @@ -1,9 +1,11 @@ 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; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; /** * Command to list all players connected to the proxy. @@ -11,11 +13,16 @@ import net.md_5.bungee.UserConnection; public class CommandList extends Command { + public CommandList() + { + super("list", "bungeecord.command.list"); + } + @Override public void execute(CommandSender sender, String[] args) { StringBuilder users = new StringBuilder(); - Collection connections = BungeeCord.instance.connections.values(); + Collection connections = ProxyServer.getInstance().getPlayers(); if (connections.isEmpty()) { @@ -23,18 +30,9 @@ public class CommandList extends Command return; } - for (UserConnection con : connections) + for (ProxiedPlayer player : connections) { - switch (getPermission(con)) - { - case ADMIN: - users.append(ChatColor.RED); - break; - case MODERATOR: - users.append(ChatColor.GREEN); - break; - } - users.append(con.username); + users.append(player.getDisplayName()); users.append(", "); users.append(ChatColor.RESET); } diff --git a/proxy/src/main/java/net/md_5/bungee/command/CommandReload.java b/proxy/src/main/java/net/md_5/bungee/command/CommandReload.java new file mode 100644 index 00000000..99a47ecc --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandReload.java @@ -0,0 +1,22 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; + +public class CommandReload extends Command +{ + + public CommandReload() + { + super("greload", "bungeecord.command.reload"); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + BungeeCord.getInstance().config.load(); + sender.sendMessage(ChatColor.GREEN + "Reloaded config, please restart if you have any issues"); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/command/CommandServer.java b/proxy/src/main/java/net/md_5/bungee/command/CommandServer.java new file mode 100644 index 00000000..1c518efc --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandServer.java @@ -0,0 +1,56 @@ +package net.md_5.bungee.command; + +import java.util.Map; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +/** + * Command to list and switch a player between available servers. + */ +public class CommandServer extends Command +{ + + public CommandServer() + { + super("server", "bungeecord.command.server"); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + if (!(sender instanceof ProxiedPlayer)) + { + return; + } + ProxiedPlayer player = (ProxiedPlayer) sender; + Map servers = BungeeCord.getInstance().config.getServers(); + if (args.length == 0) + { + StringBuilder serverList = new StringBuilder(); + for (String server : servers.keySet()) + { + serverList.append(server); + serverList.append(", "); + } + serverList.setLength(serverList.length() - 2); + player.sendMessage(ChatColor.GOLD + "You may connect to the following servers at this time: " + serverList.toString()); + } else + { + ServerInfo server = servers.get(args[0]); + if (server == null) + { + player.sendMessage(ChatColor.RED + "The specified server does not exist"); + } else if (server.equals(player.getServer().getInfo())) + { + player.sendMessage(ChatColor.RED + "You are already on this server."); + } else + { + player.connect(server); + } + } + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java b/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java new file mode 100644 index 00000000..3c637882 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java @@ -0,0 +1,63 @@ +package net.md_5.bungee.command; + +import java.util.Collection; +import java.util.Collections; +import lombok.Getter; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; + +/** + * Command sender representing the proxy console. + */ +public class ConsoleCommandSender implements CommandSender +{ + + @Getter + private static final ConsoleCommandSender instance = new ConsoleCommandSender(); + + private ConsoleCommandSender() + { + } + + @Override + public void sendMessage(String message) + { + System.out.println(ChatColor.stripColor(message)); + } + + @Override + public String getName() + { + return "CONSOLE"; + } + + @Override + public Collection getGroups() + { + return Collections.emptySet(); + } + + @Override + public void addGroups(String... groups) + { + throw new UnsupportedOperationException("Console may not have groups"); + } + + @Override + public void removeGroups(String... groups) + { + throw new UnsupportedOperationException("Console may not have groups"); + } + + @Override + public boolean hasPermission(String permission) + { + return true; + } + + @Override + public void setPermission(String permission, boolean value) + { + throw new UnsupportedOperationException("Console has all permissions"); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/config/Configuration.java b/proxy/src/main/java/net/md_5/bungee/config/Configuration.java new file mode 100644 index 00000000..0e6c73e1 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/config/Configuration.java @@ -0,0 +1,84 @@ +package net.md_5.bungee.config; + +import com.google.common.base.Preconditions; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.tablist.GlobalPingTabList; +import net.md_5.bungee.tablist.GlobalTabList; +import net.md_5.bungee.tablist.ServerUniqueTabList; + +/** + * Core configuration for the proxy. + */ +@Getter +public class Configuration +{ + + /** + * The default tab list options available for picking. + */ + private enum DefaultTabList + { + + GLOBAL, GLOBAL_PING, SERVER; + } + /** + * Time before users are disconnected due to no network activity. + */ + private int timeout = 30000; + /** + * UUID used for metrics. + */ + private String uuid = UUID.randomUUID().toString(); + /** + * Set of all listeners. + */ + private Collection listeners; + /** + * Set of all servers. + */ + private Map servers; + + public void load() + { + ConfigurationAdapter adapter = ProxyServer.getInstance().getConfigurationAdapter(); + + timeout = adapter.getInt("timeout", timeout); + uuid = adapter.getString("stats", uuid); + + DefaultTabList tab = DefaultTabList.valueOf(adapter.getString("tab_list", "GLOBAL_PING")); + if (tab == null) + { + tab = DefaultTabList.GLOBAL_PING; + } + switch (tab) + { + case GLOBAL: + ProxyServer.getInstance().setTabListHandler(new GlobalTabList()); + break; + case GLOBAL_PING: + ProxyServer.getInstance().setTabListHandler(new GlobalPingTabList()); + break; + case SERVER: + ProxyServer.getInstance().setTabListHandler(new ServerUniqueTabList()); + break; + } + + listeners = adapter.getListeners(); + Preconditions.checkArgument(listeners != null && !listeners.isEmpty(), "No listeners defined."); + + servers = adapter.getServers(); + Preconditions.checkArgument(servers != null && !servers.isEmpty(), "No servers defined"); + + for (ListenerInfo listener : listeners) + { + Preconditions.checkArgument(servers.containsKey(listener.getDefaultServer())); + } + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/config/YamlConfig.java b/proxy/src/main/java/net/md_5/bungee/config/YamlConfig.java new file mode 100644 index 00000000..a3da837e --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/config/YamlConfig.java @@ -0,0 +1,205 @@ +package net.md_5.bungee.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.logging.Level; +import net.md_5.bungee.Util; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +public class YamlConfig implements ConfigurationAdapter +{ + + private boolean loaded; + private Yaml yaml; + private Map config; + private final File file = new File("config.yml"); + + public void load() + { + try + { + file.createNewFile(); + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + yaml = new Yaml(options); + + try (InputStream is = new FileInputStream(file)) + { + config = (Map) yaml.load(is); + } + + if (config == null) + { + config = new HashMap(); + } + + loaded = true; + } catch (IOException ex) + { + throw new RuntimeException("Could not load configuration!", ex); + } + + Map permissions = get("permissions", new HashMap()); + if (permissions.isEmpty()) + { + permissions.put("default", Arrays.asList(new String[] + { + "bungeecord.command.server", "bungeecord.command.list" + })); + permissions.put("admin", Arrays.asList(new String[] + { + "bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload" + })); + } + + Map groups = get("groups", new HashMap()); + if (groups.isEmpty()) + { + groups.put("md_5", Collections.singletonList("admin")); + } + } + + private T get(String path, T def) + { + if (!loaded) + { + load(); + } + return get(path, def, config); + } + + @SuppressWarnings("unchecked") + private T get(String path, T def, Map submap) + { + if (!loaded) + { + load(); + } + + int index = path.indexOf('.'); + if (index == -1) + { + Object val = submap.get(path); + if (val == null) + { + val = def; + submap.put(path, def); + save(); + } + return (T) val; + } else + { + String first = path.substring(0, index); + String second = path.substring(index + 1, path.length()); + Map sub = (Map) submap.get(first); + return (sub != null) ? get(second, def, sub) : def; + } + } + + private void save() + { + try + { + try (FileWriter wr = new FileWriter(file)) + { + yaml.dump(config, wr); + } + } catch (IOException ex) + { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not save config", ex); + } + } + + @Override + public int getInt(String path, int def) + { + return get(path, def); + } + + @Override + public String getString(String path, String def) + { + return get(path, def); + } + + @Override + @SuppressWarnings("unchecked") + public Map getServers() + { + Map> base = get("servers", (Map) Collections.singletonMap("lobby", new HashMap<>())); + Map ret = new HashMap<>(); + + for (Map.Entry> entry : base.entrySet()) + { + Map val = entry.getValue(); + String name = entry.getKey(); + String addr = get("address", "localhost:25565", val); + InetSocketAddress address = Util.getAddr(addr); + ServerInfo info = new ServerInfo(name, address); + ret.put(name, info); + } + + return ret; + } + + @Override + @SuppressWarnings("unchecked") + public Collection getListeners() + { + Collection> base = get("listeners", (Collection) Arrays.asList(new Map[] + { + new HashMap() + })); + Map forcedDef = new HashMap<>(); + forcedDef.put("pvp.md-5.net", "pvp"); + + Collection ret = new HashSet<>(); + + for (Map val : base) + { + String motd = get("motd", "Another Bungee server", val); + int maxPlayers = get("max_players", 1, val); + String defaultServer = get("default_server", "lobby", val); + boolean forceDefault = get("force_default_server", false, val); + String host = get("host", "0.0.0.0:25577", val); + InetSocketAddress address = Util.getAddr(host); + Map forced = get("forced_hosts", forcedDef, val); + ListenerInfo info = new ListenerInfo(address, motd, maxPlayers, defaultServer, forceDefault, forced); + ret.add(info); + } + + return ret; + } + + @Override + @SuppressWarnings("unchecked") + public Collection getGroups(String player) + { + Collection groups = get("groups." + player, Collections.EMPTY_LIST); + Collection ret = new HashSet<>(groups); + ret.add("default"); + return ret; + } + + @Override + @SuppressWarnings("unchecked") + public Collection getPermissions(String group) + { + return get("permissions." + group, Collections.EMPTY_SET); + } +} diff --git a/src/main/java/net/md_5/bungee/packet/DefinedPacket.java b/proxy/src/main/java/net/md_5/bungee/packet/DefinedPacket.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/DefinedPacket.java rename to proxy/src/main/java/net/md_5/bungee/packet/DefinedPacket.java diff --git a/src/main/java/net/md_5/bungee/packet/Packet0KeepAlive.java b/proxy/src/main/java/net/md_5/bungee/packet/Packet0KeepAlive.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/Packet0KeepAlive.java rename to proxy/src/main/java/net/md_5/bungee/packet/Packet0KeepAlive.java diff --git a/src/main/java/net/md_5/bungee/packet/Packet1Login.java b/proxy/src/main/java/net/md_5/bungee/packet/Packet1Login.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/Packet1Login.java rename to proxy/src/main/java/net/md_5/bungee/packet/Packet1Login.java diff --git a/src/main/java/net/md_5/bungee/packet/Packet2Handshake.java b/proxy/src/main/java/net/md_5/bungee/packet/Packet2Handshake.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/Packet2Handshake.java rename to proxy/src/main/java/net/md_5/bungee/packet/Packet2Handshake.java diff --git a/src/main/java/net/md_5/bungee/packet/Packet3Chat.java b/proxy/src/main/java/net/md_5/bungee/packet/Packet3Chat.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/Packet3Chat.java rename to proxy/src/main/java/net/md_5/bungee/packet/Packet3Chat.java diff --git a/src/main/java/net/md_5/bungee/packet/Packet9Respawn.java b/proxy/src/main/java/net/md_5/bungee/packet/Packet9Respawn.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/Packet9Respawn.java rename to proxy/src/main/java/net/md_5/bungee/packet/Packet9Respawn.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketC9PlayerListItem.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketC9PlayerListItem.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/PacketC9PlayerListItem.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketC9PlayerListItem.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketCDClientStatus.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketCDClientStatus.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/PacketCDClientStatus.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketCDClientStatus.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketFAPluginMessage.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketFAPluginMessage.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/PacketFAPluginMessage.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketFAPluginMessage.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketFCEncryptionResponse.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketFCEncryptionResponse.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/PacketFCEncryptionResponse.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketFCEncryptionResponse.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketFDEncryptionRequest.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketFDEncryptionRequest.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/PacketFDEncryptionRequest.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketFDEncryptionRequest.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketFFKick.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketFFKick.java similarity index 100% rename from src/main/java/net/md_5/bungee/packet/PacketFFKick.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketFFKick.java diff --git a/src/main/java/net/md_5/bungee/packet/PacketInputStream.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketInputStream.java similarity index 94% rename from src/main/java/net/md_5/bungee/packet/PacketInputStream.java rename to proxy/src/main/java/net/md_5/bungee/packet/PacketInputStream.java index 66753a83..30a39ac1 100644 --- a/src/main/java/net/md_5/bungee/packet/PacketInputStream.java +++ b/proxy/src/main/java/net/md_5/bungee/packet/PacketInputStream.java @@ -2,11 +2,8 @@ package net.md_5.bungee.packet; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import net.md_5.bungee.Util; -import net.md_5.mendax.PacketDefinitions; import net.md_5.mendax.datainput.DataInputPacketReader; /** diff --git a/proxy/src/main/java/net/md_5/bungee/tablist/GlobalPingTabList.java b/proxy/src/main/java/net/md_5/bungee/tablist/GlobalPingTabList.java new file mode 100644 index 00000000..ba108cec --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/tablist/GlobalPingTabList.java @@ -0,0 +1,32 @@ +package net.md_5.bungee.tablist; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.packet.PacketC9PlayerListItem; + +public class GlobalPingTabList extends GlobalTabList +{ + + private static final int PING_THRESHOLD = 20; + private final Map lastPings = new ConcurrentHashMap<>(); + + @Override + public void onDisconnect(ProxiedPlayer player) + { + lastPings.remove(player); + super.onDisconnect(player); + } + + @Override + public void onPingChange(ProxiedPlayer player, int ping) + { + Integer lastPing = lastPings.get(player); + if (lastPing == null || (ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing)) + { + BungeeCord.getInstance().broadcast(new PacketC9PlayerListItem(player.getDisplayName(), true, ping)); + lastPings.put(player, ping); + } + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java b/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java new file mode 100644 index 00000000..f22d6ee6 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java @@ -0,0 +1,55 @@ +package net.md_5.bungee.tablist; + +import java.util.HashSet; +import java.util.Set; +import lombok.Synchronized; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.TabListHandler; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.packet.PacketC9PlayerListItem; + +public class GlobalTabList implements TabListHandler +{ + + private final Set sentPings = new HashSet<>(); + + @Override + public void onConnect(ProxiedPlayer player) + { + for (UserConnection c : BungeeCord.getInstance().connections.values()) + { + c.packetQueue.add(new PacketC9PlayerListItem(c.displayName, true, c.getPing())); + } + } + + @Override + @Synchronized(value = "sentPings") + public void onPingChange(ProxiedPlayer player, int ping) + { + if (!sentPings.contains(player)) + { + BungeeCord.getInstance().broadcast(new PacketC9PlayerListItem(player.getDisplayName(), true, player.getPing())); + sentPings.add(player); + } + } + + @Override + @Synchronized(value = "sentPings") + public void onDisconnect(ProxiedPlayer player) + { + BungeeCord.getInstance().broadcast(new PacketC9PlayerListItem(player.getDisplayName(), false, 9999)); + sentPings.remove(player); + } + + @Override + public void onServerChange(ProxiedPlayer player) + { + } + + @Override + public boolean onListUpdate(ProxiedPlayer player, String name, boolean online, int ping) + { + return false; + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/tablist/ServerUniqueTabList.java b/proxy/src/main/java/net/md_5/bungee/tablist/ServerUniqueTabList.java new file mode 100644 index 00000000..f5cef3a8 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/tablist/ServerUniqueTabList.java @@ -0,0 +1,73 @@ +package net.md_5.bungee.tablist; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.TabListHandler; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.packet.PacketC9PlayerListItem; + +public class ServerUniqueTabList implements TabListHandler +{ + + private final Map> sentUsernames = new ConcurrentHashMap<>(); + + @Override + public void onConnect(ProxiedPlayer player) + { + } + + @Override + public void onPingChange(ProxiedPlayer player, int ping) + { + } + + @Override + public void onDisconnect(ProxiedPlayer player) + { + sentUsernames.remove(player); + } + + @Override + public void onServerChange(ProxiedPlayer player) + { + Set usernames = sentUsernames.get(player); + if (usernames != null) + { + synchronized (usernames) + { + for (String username : usernames) + { + ((UserConnection) player).packetQueue.add(new PacketC9PlayerListItem(username, false, 9999)); + } + usernames.clear(); + } + } + } + + @Override + public boolean onListUpdate(ProxiedPlayer player, String name, boolean online, int ping) + { + Set usernames = sentUsernames.get(player); + if (usernames == null) + { + usernames = new HashSet<>(); + sentUsernames.put(player, usernames); + } + + synchronized (usernames) + { + if (online) + { + usernames.add(name); + } else + { + usernames.remove(name); + } + } + + return true; + } +} diff --git a/src/main/java/net/md_5/bungee/BungeeCord.java b/src/main/java/net/md_5/bungee/BungeeCord.java deleted file mode 100644 index 382fe191..00000000 --- a/src/main/java/net/md_5/bungee/BungeeCord.java +++ /dev/null @@ -1,284 +0,0 @@ -package net.md_5.bungee; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import static net.md_5.bungee.Logger.$; -import net.md_5.bungee.command.*; -import net.md_5.bungee.packet.DefinedPacket; -import net.md_5.bungee.packet.PacketFAPluginMessage; -import net.md_5.bungee.plugin.JavaPluginManager; -import net.md_5.bungee.tablist.GlobalPingTabList; -import net.md_5.bungee.tablist.GlobalTabList; -import net.md_5.bungee.tablist.ServerUniqueTabList; -import net.md_5.bungee.tablist.TabListHandler; - -/** - * Main BungeeCord proxy class. - */ -public class BungeeCord -{ - - /** - * Server protocol version. - */ - public static final int PROTOCOL_VERSION = 51; - /** - * Server game version. - */ - public static final String GAME_VERSION = "1.4.6"; - /** - * Current software instance. - */ - public static BungeeCord instance; - /** - * Current operation state. - */ - public volatile boolean isRunning; - /** - * Configuration. - */ - public final Configuration config = new Configuration(); - /** - * Thread pool. - */ - public final ExecutorService threadPool = Executors.newCachedThreadPool(); - /** - * locations.yml save thread. - */ - private final ReconnectSaveThread saveThread = new ReconnectSaveThread(); - /** - * Server socket listener. - */ - private ListenThread listener; - /** - * Current version. - */ - public static String version = (BungeeCord.class.getPackage().getImplementationVersion() == null) ? "unknown" : BungeeCord.class.getPackage().getImplementationVersion(); - /** - * Fully qualified connections. - */ - public Map connections = new ConcurrentHashMap<>(); - /** - * Registered commands. - */ - public Map commandMap = new HashMap<>(); - /** - * Tab list handler - */ - public TabListHandler tabListHandler; - /** - * Registered Global Plugin Channels - */ - public Queue globalPluginChannels = new ConcurrentLinkedQueue<>(); - /** - * Plugin manager. - */ - public final JavaPluginManager pluginManager = new JavaPluginManager(); - - - { - commandMap.put("greload",new CommandReload()); - commandMap.put("end", new CommandEnd()); - commandMap.put("glist", new CommandList()); - commandMap.put("server", new CommandServer()); - commandMap.put("ip", new CommandIP()); - commandMap.put("alert", new CommandAlert()); - commandMap.put("motd", new CommandMotd()); - commandMap.put("bungee", new CommandBungee()); - } - - /** - * Starts a new instance of BungeeCord. - * - * @param args command line arguments, currently none are used - * @throws IOException when the server cannot be started - */ - public static void main(String[] args) throws IOException - { - instance = new BungeeCord(); - $().info("Enabled BungeeCord version " + instance.version); - instance.start(); - - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - while (instance.isRunning) - { - String line = br.readLine(); - if (line != null) - { - boolean handled = instance.dispatchCommand(line, ConsoleCommandSender.instance); - if (!handled) - { - System.err.println("Command not found"); - } - } - } - } - - /** - * Dispatch a command by formatting the arguments and then executing it. - * - * @param commandLine the entire command and arguments string - * @param sender which executed the command - * @return whether the command was handled or not. - */ - public boolean dispatchCommand(String commandLine, CommandSender sender) - { - String[] split = commandLine.trim().split(" "); - String commandName = split[0].toLowerCase(); - Command command = commandMap.get(commandName); - if (config.disabledCommands != null && config.disabledCommands.contains(commandName)) - { - return false; - } else if (command != null) - { - String[] args = Arrays.copyOfRange(split, 1, split.length); - try - { - command.execute(sender, args); - } catch (Exception ex) - { - sender.sendMessage(ChatColor.RED + "An error occurred while executing this command!"); - $().severe("----------------------- [Start of command error] -----------------------"); - $().log(Level.SEVERE, "", ex); - $().severe("----------------------- [End of command error] -----------------------"); - } - } - - return command != null; - } - - /** - * Start this proxy instance by loading the configuration, plugins and - * starting the connect thread. - * - * @throws IOException - */ - public void start() throws IOException - { - config.load(); - isRunning = true; - - pluginManager.loadPlugins(); - - switch (config.tabList) - { - default: - case 1: - tabListHandler = new GlobalPingTabList(); - break; - case 2: - tabListHandler = new GlobalTabList(); - break; - case 3: - tabListHandler = new ServerUniqueTabList(); - break; - } - - // Add RubberBand to the global plugin channel list - globalPluginChannels.add("RubberBand"); - - InetSocketAddress addr = Util.getAddr(config.bindHost); - listener = new ListenThread(addr); - listener.start(); - - saveThread.start(); - $().info("Listening on " + addr); - - if (config.metricsEnabled) - { - new Metrics().start(); - } - } - - /** - * Destroy this proxy instance cleanly by kicking all users, saving the - * configuration and closing all sockets. - */ - public void stop() - { - this.isRunning = false; - $().info("Disabling plugin"); - pluginManager.onDisable(); - - $().info("Closing listen thread"); - try - { - listener.socket.close(); - listener.join(); - } catch (InterruptedException | IOException ex) - { - $().severe("Could not close listen thread"); - } - - $().info("Closing pending connections"); - threadPool.shutdown(); - - $().info("Disconnecting " + connections.size() + " connections"); - for (UserConnection user : connections.values()) - { - user.disconnect("Proxy restarting, brb."); - } - - $().info("Saving reconnect locations"); - saveThread.interrupt(); - try - { - saveThread.join(); - } catch (InterruptedException ex) - { - } - - $().info("Thank you and goodbye"); - System.exit(0); - } - - /** - * Miscellaneous method to set options on a socket based on those in the - * configuration. - * - * @param socket to set the options on - * @throws IOException when the underlying set methods thrown an exception - */ - public void setSocketOptions(Socket socket) throws IOException - { - socket.setSoTimeout(config.timeout); - socket.setTrafficClass(0x18); - socket.setTcpNoDelay(true); - } - - /** - * Broadcasts a packet to all clients that is connected to this instance. - * - * @param packet the packet to send - */ - public void broadcast(DefinedPacket packet) - { - for (UserConnection con : connections.values()) - { - con.packetQueue.add(packet); - } - } - - /** - * Register a plugin channel for all users - * - * @param channel name - */ - public void registerPluginChannel(String channel) - { - globalPluginChannels.add(channel); - broadcast(new PacketFAPluginMessage("REGISTER", channel.getBytes())); - } -} diff --git a/src/main/java/net/md_5/bungee/Configuration.java b/src/main/java/net/md_5/bungee/Configuration.java deleted file mode 100644 index 7ad2f2a8..00000000 --- a/src/main/java/net/md_5/bungee/Configuration.java +++ /dev/null @@ -1,314 +0,0 @@ -package net.md_5.bungee; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import static net.md_5.bungee.Logger.$; -import net.md_5.bungee.command.CommandSender; -import net.md_5.bungee.command.ConsoleCommandSender; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; - -/** - * Core configuration for the proxy. - */ -public class Configuration -{ - - /** - * Reconnect locations file. - */ - private transient File reconnect = new File("locations.yml"); - /** - * Loaded reconnect locations. - */ - private transient Map reconnectLocations; - /** - * Config file. - */ - private transient File file = new File("config.yml"); - /** - * Yaml instance. - */ - private transient Yaml yaml; - /** - * Loaded config. - */ - private transient Map config; - /** - * Bind host. - */ - public String bindHost = "0.0.0.0:25577"; - /** - * Server ping motd. - */ - public String motd = "BungeeCord Proxy Instance"; - /** - * Name of default server. - */ - public String defaultServerName = "default"; - /** - * Max players as displayed in list ping. Soft limit. - */ - public int maxPlayers = 1; - /** - * Tab list 1: For a tab list that is global over all server (using their - * Minecraft name) and updating their ping frequently 2: Same as 1 but does - * not update their ping frequently, just once, 3: Makes the individual - * servers handle the tab list (server unique). - */ - public int tabList = 1; - /** - * Socket timeout. - */ - public int timeout = 15000; - /** - * All servers. - */ - public Map servers = new HashMap() - { - - { - put(defaultServerName, "127.0.0.1:1338"); - put("pvp", "127.0.0.1:1337"); - } - }; - /** - * Forced servers. - */ - public Map forcedServers = new HashMap() - { - - { - put("pvp.md-5.net", "pvp"); - } - }; - /** - * Proxy admins. - */ - public List admins = new ArrayList() - { - - { - add("Insert Admins Here"); - } - }; - /** - * Proxy moderators. - */ - public List moderators = new ArrayList() - { - - { - add("Insert Moderators Here"); - } - }; - /** - * Commands which will be blocked completely. - */ - public List disabledCommands = new ArrayList() - { - - { - add("glist"); - } - }; - /** - * Maximum number of lines to log before old ones are removed. - */ - public int logNumLines = 1 << 14; - /** - * UUID for Metrics. - */ - public String statsUuid = UUID.randomUUID().toString(); - public boolean metricsEnabled = true; - public boolean forceDefaultServer = false; - - /** - * Load the configuration and save default values. - */ - public void load() - { - try - { - file.createNewFile(); - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - yaml = new Yaml(options); - - try (InputStream is = new FileInputStream(file)) - { - config = (Map) yaml.load(is); - } - - if (config == null) - { - config = new LinkedHashMap<>(); - } - - $().info("-------------- Loading configuration ----------------"); - for (Field field : getClass().getDeclaredFields()) - { - if (!Modifier.isTransient(field.getModifiers())) - { - String name = Util.normalize(field.getName()); - try - { - Object def = field.get(this); - Object value = get(name, def); - - field.set(this, value); - - $().info(name + ": " + value); - } catch (IllegalAccessException ex) - { - $().severe("Could not get config node: " + name); - } - } - } - $().info("-----------------------------------------------------"); - - if (servers.get(defaultServerName) == null) - { - throw new IllegalArgumentException("Server '" + defaultServerName + "' not defined"); - } - - if (forcedServers != null) - { - for (String server : forcedServers.values()) - { - if (!servers.containsKey(server)) - { - throw new IllegalArgumentException("Forced server " + server + " is not defined in servers"); - } - } - } - - motd = ChatColor.translateAlternateColorCodes('&', motd); - - reconnect.createNewFile(); - try (FileInputStream recon = new FileInputStream(reconnect)) - { - reconnectLocations = (Map) yaml.load(recon); - } - if (reconnectLocations == null) - { - reconnectLocations = new LinkedHashMap<>(); - } - - } catch (IOException ex) - { - $().severe("Could not load config!"); - ex.printStackTrace(); - } - } - - private T get(String path, T def) - { - if (!config.containsKey(path)) - { - config.put(path, def); - save(file, config); - } - return (T) config.get(path); - } - - private void save(File fileToSave, Map toSave) - { - try - { - try (FileWriter wr = new FileWriter(fileToSave)) - { - yaml.dump(toSave, wr); - } - } catch (IOException ex) - { - $().severe("Could not save config file " + fileToSave); - ex.printStackTrace(); - } - } - - /** - * Get which server a user should be connected to, taking into account their - * name and virtual host. - * - * @param user to get a server for - * @param requestedHost the host which they connected to - * @return the name of the server which they should be connected to. - */ - public String getServer(String user, String requestedHost) - { - String server = (forcedServers == null) ? null : forcedServers.get(requestedHost.toLowerCase()); - if (server == null) - { - server = reconnectLocations.get(user); - } - if (server == null) - { - server = defaultServerName; - } - return server; - } - - /** - * Save the last server which the user was on. - * - * @param user the name of the user - * @param server which they were last on - */ - public void setServer(UserConnection user, String server) - { - reconnectLocations.put(user.username, server); - } - - /** - * Gets the connectable address of a server defined in the configuration. - * - * @param name the friendly name of a server - * @return the usable {@link InetSocketAddress} mapped to this server - */ - public InetSocketAddress getServer(String name) - { - String server = servers.get((name == null) ? defaultServerName : name); - return (server != null) ? Util.getAddr(server) : getServer(null); - } - - /** - * Save the current mappings of users to servers. - */ - public void saveHosts() - { - save(reconnect, reconnectLocations); - $().info("Saved reconnect locations to " + reconnect); - } - - /** - * Get the highest permission a player has. - * - * @param sender to get permissions of - * @return their permission - */ - public Permission getPermission(CommandSender sender) - { - Permission permission = Permission.DEFAULT; - if (admins.contains(sender.getName()) || sender instanceof ConsoleCommandSender) - { - permission = Permission.ADMIN; - } else if (moderators.contains(sender.getName())) - { - permission = Permission.MODERATOR; - } - return permission; - } -} diff --git a/src/main/java/net/md_5/bungee/Permission.java b/src/main/java/net/md_5/bungee/Permission.java deleted file mode 100644 index 7a912ad7..00000000 --- a/src/main/java/net/md_5/bungee/Permission.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.md_5.bungee; - -public enum Permission -{ - - /** - * Can access all commands. - */ - ADMIN, - /** - * Can access commands which do not affect everyone. - */ - MODERATOR, - /** - * Can access other commands. - */ - DEFAULT; -} diff --git a/src/main/java/net/md_5/bungee/ReconnectSaveThread.java b/src/main/java/net/md_5/bungee/ReconnectSaveThread.java deleted file mode 100644 index 92745688..00000000 --- a/src/main/java/net/md_5/bungee/ReconnectSaveThread.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.md_5.bungee; - -/** - * Class to call the {@link Configuration#saveHosts() } method at 5 minute - * intervals. - */ -public class ReconnectSaveThread extends Thread -{ - - public ReconnectSaveThread() - { - super("Location Save Thread"); - setPriority(Thread.MIN_PRIORITY); - } - - @Override - public void run() - { - while (BungeeCord.instance.isRunning) - { - try - { - Thread.sleep(5 * 1000 * 60); // 5 minutes - } catch (InterruptedException ex) - { - } - BungeeCord.instance.config.saveHosts(); - } - } -} diff --git a/src/main/java/net/md_5/bungee/UserConnection.java b/src/main/java/net/md_5/bungee/UserConnection.java deleted file mode 100644 index e6177578..00000000 --- a/src/main/java/net/md_5/bungee/UserConnection.java +++ /dev/null @@ -1,356 +0,0 @@ -package net.md_5.bungee; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import net.md_5.bungee.command.CommandSender; -import net.md_5.bungee.packet.*; -import net.md_5.bungee.plugin.ChatEvent; -import net.md_5.bungee.plugin.PluginMessageEvent; -import net.md_5.bungee.plugin.PluginMessageEvent.Destination; -import net.md_5.bungee.plugin.ServerConnectEvent; - -public class UserConnection extends GenericConnection implements CommandSender -{ - - public final Packet2Handshake handshake; - public Queue packetQueue = new ConcurrentLinkedQueue<>(); - public List loginPackets = new ArrayList<>(); - private ServerConnection server; - private UpstreamBridge upBridge; - private DownstreamBridge downBridge; - // reconnect stuff - private int clientEntityId; - private int serverEntityId; - private volatile boolean reconnecting; - // ping stuff - private int trackingPingId; - private long pingTime; - private int ping; - public UserConnection instance = this; - - public UserConnection(Socket socket, PacketInputStream in, OutputStream out, Packet2Handshake handshake, List loginPackets) - { - super(socket, in, out); - this.handshake = handshake; - username = handshake.username; - tabListName = handshake.username; - this.loginPackets = loginPackets; - BungeeCord.instance.connections.put(username, this); - BungeeCord.instance.tabListHandler.onJoin(this); - } - - public void setTabListName(String newName) - { - BungeeCord.instance.tabListHandler.onDisconnect(this); - tabListName = newName; - BungeeCord.instance.tabListHandler.onJoin(this); - } - - public void connect(String server) - { - ServerConnectEvent event = new ServerConnectEvent(this.server == null, this, server); - event.setNewServer(server); - BungeeCord.instance.pluginManager.onServerConnect(event); - if (event.getMessage() != null) - { - this.sendMessage(event.getMessage()); - } - if (event.getNewServer() == null) - { - if (event.isFirstTime()) - { - event.setNewServer(BungeeCord.instance.config.defaultServerName); - } else - { - return; - } - } - InetSocketAddress addr = BungeeCord.instance.config.getServer(event.getNewServer()); - connect(server, addr); - } - - private void connect(String name, InetSocketAddress serverAddr) - { - BungeeCord.instance.tabListHandler.onServerChange(this); - try - { - reconnecting = true; - - if (server != null) - { - 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()); - } - - ServerConnection newServer = ServerConnection.connect(this, name, serverAddr, handshake, true); - if (server == null) - { - clientEntityId = newServer.loginPacket.entityId; - serverEntityId = newServer.loginPacket.entityId; - out.write(newServer.loginPacket.getPacket()); - upBridge = new UpstreamBridge(); - upBridge.start(); - } else - { - try - { - downBridge.interrupt(); - downBridge.join(); - } catch (InterruptedException ie) - { - } - - server.disconnect("Quitting"); - - Packet1Login login = newServer.loginPacket; - serverEntityId = login.entityId; - out.write(new Packet9Respawn(login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType).getPacket()); - } - reconnecting = false; - downBridge = new DownstreamBridge(); - server = newServer; - downBridge.start(); - } catch (KickException ex) - { - destroySelf(ex.getMessage()); - } catch (Exception ex) - { - destroySelf("Could not connect to server - " + ex.getClass().getSimpleName()); - ex.printStackTrace(); // TODO: Remove - } - } - - public String getServer() - { - return server.name; - } - - public SocketAddress getAddress() - { - return socket.getRemoteSocketAddress(); - } - - public int getPing() - { - return ping; - } - - private void setPing(int ping) - { - BungeeCord.instance.tabListHandler.onPingChange(this, ping); - this.ping = ping; - } - - private void destroySelf(String reason) - { - if (BungeeCord.instance.isRunning) - { - BungeeCord.instance.connections.remove(username); - } - disconnect(reason); - if (server != null) - { - server.disconnect("Quitting"); - BungeeCord.instance.config.setServer(this, server.name); - } - } - - @Override - public void disconnect(String reason) - { - BungeeCord.instance.tabListHandler.onDisconnect(this); - super.disconnect(reason); - } - - @Override - public void sendMessage(String message) - { - packetQueue.add(new Packet3Chat(message)); - } - - public void sendPluginMessage(String tag, byte[] data) - { - server.packetQueue.add(new PacketFAPluginMessage(tag, data)); - } - - @Override - public String getName() - { - return username; - } - - private class UpstreamBridge extends Thread - { - - public UpstreamBridge() - { - super("Upstream Bridge - " + username); - } - - @Override - public void run() - { - while (!socket.isClosed()) - { - try - { - byte[] packet = in.readPacket(); - boolean sendPacket = true; - - int id = Util.getId(packet); - if (id == 0xFA) - { - // Call the onPluginMessage event - PacketFAPluginMessage message = new PacketFAPluginMessage(packet); - PluginMessageEvent event = new PluginMessageEvent(Destination.SERVER, instance); - event.setTag(message.tag); - event.setData(new String(message.data)); - BungeeCord.instance.pluginManager.onPluginMessage(event); - - if (event.isCancelled()) - { - continue; - } - } else 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 - { - ChatEvent chatEvent = new ChatEvent(ChatEvent.Destination.SERVER, instance); - chatEvent.setText(message); - BungeeCord.instance.pluginManager.onChat(chatEvent); - sendPacket = !chatEvent.isCancelled(); - } - } else if (id == 0x00) - { - if (trackingPingId == new Packet0KeepAlive(packet).id) - { - setPing((int) (System.currentTimeMillis() - pingTime)); - } - } - - while (!server.packetQueue.isEmpty()) - { - DefinedPacket p = server.packetQueue.poll(); - if (p != null) - { - server.out.write(p.getPacket()); - } - } - - EntityMap.rewrite(packet, clientEntityId, serverEntityId); - if (sendPacket && !server.socket.isClosed()) - { - server.out.write(packet); - } - } catch (IOException ex) - { - destroySelf("Reached end of stream"); - } catch (Exception ex) - { - destroySelf(Util.exception(ex)); - } - } - } - } - - private class DownstreamBridge extends Thread - { - - public DownstreamBridge() - { - super("Downstream Bridge - " + username); - } - - @Override - public void run() - { - try - { - while (!reconnecting) - { - byte[] packet = server.in.readPacket(); - - int id = Util.getId(packet); - if (id == 0xFA) - { - // Call the onPluginMessage event - PacketFAPluginMessage message = new PacketFAPluginMessage(packet); - PluginMessageEvent event = new PluginMessageEvent(Destination.CLIENT, instance); - event.setTag(message.tag); - event.setData(new String(message.data)); - BungeeCord.instance.pluginManager.onPluginMessage(event); - - if (event.isCancelled()) - { - continue; - } - - message.tag = event.getTag(); - message.data = event.getData().getBytes(); - - // Allow a message for killing the connection outright - if (message.tag.equals("KillCon")) - { - break; - } - - if (message.tag.equals("RubberBand")) - { - String server = new String(message.data); - connect(server); - break; - } - } else if (id == 0x00) - { - trackingPingId = new Packet0KeepAlive(packet).id; - pingTime = System.currentTimeMillis(); - } else if (id == 0x03) - { - Packet3Chat chat = new Packet3Chat(packet); - String message = chat.message; - ChatEvent chatEvent = new ChatEvent(ChatEvent.Destination.CLIENT, instance); - chatEvent.setText(message); - BungeeCord.instance.pluginManager.onChat(chatEvent); - if (chatEvent.isCancelled()) - { - continue; - } - } else if (id == 0xC9) - { - if (!BungeeCord.instance.tabListHandler.onPacketC9(UserConnection.this, new PacketC9PlayerListItem(packet))) - { - continue; - } - } - - while (!packetQueue.isEmpty()) - { - DefinedPacket p = packetQueue.poll(); - if (p != null) - { - out.write(p.getPacket()); - } - } - - EntityMap.rewrite(packet, serverEntityId, clientEntityId); - out.write(packet); - } - } catch (Exception ex) - { - destroySelf(Util.exception(ex)); - } - } - } -} diff --git a/src/main/java/net/md_5/bungee/command/Command.java b/src/main/java/net/md_5/bungee/command/Command.java deleted file mode 100644 index f08336e7..00000000 --- a/src/main/java/net/md_5/bungee/command/Command.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.md_5.bungee.command; - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.Permission; - -/** - * Class which represents a proxy command. The {@link #execute(net.md_5.bungee.command.CommandSender, java.lang.String[]) - * } method will be called to dispatch the command. - */ -public abstract class Command -{ - - /** - * Execute this command. - * - * @param sender the sender executing this command - * @param args the parameters to this command, does not include the '/' or - * the original command. - */ - public abstract void execute(CommandSender sender, String[] args); - - public Permission getPermission(CommandSender sender) - { - return BungeeCord.instance.config.getPermission(sender); - } -} diff --git a/src/main/java/net/md_5/bungee/command/CommandBungee.java b/src/main/java/net/md_5/bungee/command/CommandBungee.java deleted file mode 100644 index 8ea902df..00000000 --- a/src/main/java/net/md_5/bungee/command/CommandBungee.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.md_5.bungee.command; - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.ChatColor; - -public class CommandBungee extends Command -{ - - @Override - public void execute(CommandSender sender, String[] args) - { - sender.sendMessage(ChatColor.BLUE + "This server is running BungeeCord version " + BungeeCord.version + " by md_5"); - sender.sendMessage(ChatColor.BLUE + "Your current permission level is " + getPermission(sender).name()); - } -} diff --git a/src/main/java/net/md_5/bungee/command/CommandEnd.java b/src/main/java/net/md_5/bungee/command/CommandEnd.java deleted file mode 100644 index 3750dfd1..00000000 --- a/src/main/java/net/md_5/bungee/command/CommandEnd.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.md_5.bungee.command; - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.ChatColor; -import net.md_5.bungee.Permission; - -/** - * Command to terminate the proxy instance. May only be used by the console. - */ -public class CommandEnd extends Command -{ - - @Override - public void execute(CommandSender sender, String[] args) - { - if (getPermission(sender) != Permission.ADMIN) - { - sender.sendMessage(ChatColor.RED + "You do not have permission to use this command"); - } else - { - BungeeCord.instance.stop(); - } - } -} diff --git a/src/main/java/net/md_5/bungee/command/CommandMotd.java b/src/main/java/net/md_5/bungee/command/CommandMotd.java deleted file mode 100644 index 3d26c066..00000000 --- a/src/main/java/net/md_5/bungee/command/CommandMotd.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.md_5.bungee.command; - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.ChatColor; -import net.md_5.bungee.Permission; - -/** - * Command to set a temp copy of the motd in real-time without stopping the - * proxy. - */ -public class CommandMotd extends Command -{ - - @Override - public void execute(CommandSender sender, String[] args) - { - if (getPermission(sender) != Permission.ADMIN) - { - sender.sendMessage(ChatColor.RED + "You do not have permission to use this command"); - } else - { - String newMOTD = ""; - for (String s : args) - { - newMOTD = newMOTD + s + " "; - } - newMOTD = newMOTD.substring(0, newMOTD.length() - 1); - BungeeCord.instance.config.motd = ChatColor.translateAlternateColorCodes('&', newMOTD); - } - } -} diff --git a/src/main/java/net/md_5/bungee/command/CommandReload.java b/src/main/java/net/md_5/bungee/command/CommandReload.java deleted file mode 100644 index 18c922d0..00000000 --- a/src/main/java/net/md_5/bungee/command/CommandReload.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.md_5.bungee.command; - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.ChatColor; -import net.md_5.bungee.Permission; - -public class CommandReload extends Command -{ - - @Override - public void execute(CommandSender sender, String[] args) - { - if (getPermission(sender) != Permission.ADMIN) - { - sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!"); - return; - } - BungeeCord.instance.config.load(); - sender.sendMessage(ChatColor.GREEN + "Reloaded config, please restart if you have any issues"); - } -} diff --git a/src/main/java/net/md_5/bungee/command/CommandSender.java b/src/main/java/net/md_5/bungee/command/CommandSender.java deleted file mode 100644 index fb0adf36..00000000 --- a/src/main/java/net/md_5/bungee/command/CommandSender.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.md_5.bungee.command; - -public interface CommandSender -{ - - /** - * Sends a message to the client at the earliest available opportunity. - * - * @param message the message to send - */ - public abstract void sendMessage(String message); - - /** - * Get the senders name or CONSOLE for console. - * - * @return the friendly name of the player. - */ - public abstract String getName(); -} diff --git a/src/main/java/net/md_5/bungee/command/CommandServer.java b/src/main/java/net/md_5/bungee/command/CommandServer.java deleted file mode 100644 index a2956b56..00000000 --- a/src/main/java/net/md_5/bungee/command/CommandServer.java +++ /dev/null @@ -1,48 +0,0 @@ -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; - -/** - * Command to list and switch a player between available servers. - */ -public class CommandServer extends Command -{ - - @Override - public void execute(CommandSender sender, String[] args) - { - if (!(sender instanceof UserConnection)) - { - return; - } - UserConnection con = (UserConnection) sender; - Collection servers = BungeeCord.instance.config.servers.keySet(); - if (args.length <= 0) - { - 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]; - if (!servers.contains(server)) - { - con.sendMessage(ChatColor.RED + "The specified server does not exist"); - } else if (args[0].equals(con.getServer())) - { - con.sendMessage(ChatColor.RED + "You are already on this server."); - } else - { - con.connect(server); - } - } - } -} diff --git a/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java b/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java deleted file mode 100644 index 79401a03..00000000 --- a/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.md_5.bungee.command; - -import net.md_5.bungee.ChatColor; - -/** - * Command sender representing the proxy console. - */ -public class ConsoleCommandSender implements CommandSender -{ - - public static final ConsoleCommandSender instance = new ConsoleCommandSender(); - - @Override - public void sendMessage(String message) - { - System.out.println(ChatColor.stripColor(message)); - } - - @Override - public String getName() - { - return "CONSOLE"; - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/Cancellable.java b/src/main/java/net/md_5/bungee/plugin/Cancellable.java deleted file mode 100644 index f4c9bceb..00000000 --- a/src/main/java/net/md_5/bungee/plugin/Cancellable.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.md_5.bungee.plugin; - -/** - * An event which may be canceled and this be prevented from happening. - */ -public interface Cancellable -{ - - /** - * Sets the canceled state of this event. - * - * @param canceled whether this event is canceled or not - */ - public void setCancelled(boolean canceled); - - /** - * Gets the canceled state of this event. - * - * @return whether this event is canceled or not - */ - public boolean isCancelled(); -} diff --git a/src/main/java/net/md_5/bungee/plugin/ChatEvent.java b/src/main/java/net/md_5/bungee/plugin/ChatEvent.java deleted file mode 100644 index 76fab296..00000000 --- a/src/main/java/net/md_5/bungee/plugin/ChatEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.md_5.bungee.plugin; - -import lombok.Data; -import net.md_5.bungee.UserConnection; - -@Data -public class ChatEvent implements Cancellable -{ - - /** - * Canceled state. - */ - private boolean cancelled; - /** - * Whether this packet is destined for the server or the client. - */ - private final Destination destination; - /** - * User in question. - */ - private final UserConnection connection; - /** - * Text contained in this chat. - */ - private String text; - - /** - * An enum that signifies the destination for this packet. - */ - public enum Destination - { - - SERVER, - CLIENT - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/InvalidPluginException.java b/src/main/java/net/md_5/bungee/plugin/InvalidPluginException.java deleted file mode 100644 index 1c277afd..00000000 --- a/src/main/java/net/md_5/bungee/plugin/InvalidPluginException.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.md_5.bungee.plugin; - -/** - * Exception thrown when a plugin could not be loaded for any reason. - */ -public class InvalidPluginException extends RuntimeException -{ - - private static final long serialVersionUID = 1L; - - public InvalidPluginException(String message, Throwable cause) - { - super(message, cause); - } - - public InvalidPluginException(String message) - { - super(message); - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/JavaPlugin.java b/src/main/java/net/md_5/bungee/plugin/JavaPlugin.java deleted file mode 100644 index 74bbe182..00000000 --- a/src/main/java/net/md_5/bungee/plugin/JavaPlugin.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.md_5.bungee.plugin; - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.command.Command; - -/** - * Base class which all proxy plugins should extend. - */ -public abstract class JavaPlugin -{ - - /** - * Description file. - */ - PluginDescription description; - - /** - * Called on enable. - */ - public void onEnable() - { - } - - /** - * Called on disable. - */ - public void onDisable() - { - } - - /** - * Called when a user connects with their name and address. To keep things - * simple this name has not been checked with minecraft.net. - */ - public void onHandshake(LoginEvent event) - { - } - - /** - * Called after a user has been authed with minecraftt.net and is about to - * log into the proxy. - */ - public void onLogin(LoginEvent event) - { - } - - /** - * Called when a user is connecting to a new server. - */ - public void onServerConnect(ServerConnectEvent event) - { - } - - /** - * Called when a plugin message is sent to the client or server - */ - public void onPluginMessage(PluginMessageEvent event) - { - } - - /** - * Called when a chat message is sent to the client or server - */ - public void onChat(ChatEvent event) - { - } - - /** - * Register a command for use with the proxy. - */ - protected final void registerCommand(String label, Command command) - { - BungeeCord.instance.commandMap.put(label, command); - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/JavaPluginManager.java b/src/main/java/net/md_5/bungee/plugin/JavaPluginManager.java deleted file mode 100644 index 1a695cc9..00000000 --- a/src/main/java/net/md_5/bungee/plugin/JavaPluginManager.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.md_5.bungee.plugin; - -import com.google.common.io.PatternFilenameFilter; -import java.io.File; -import java.io.InputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.HashSet; -import java.util.Set; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; -import lombok.Getter; -import static net.md_5.bungee.Logger.$; - -/** - * Plugin manager to handle loading and saving other JavaPlugin's. This class is - * itself a plugin for ease of use. - */ -public class JavaPluginManager extends JavaPlugin -{ - - /** - * Set of loaded plugins. - */ - @Getter - private final Set plugins = new HashSet<>(); - - /** - * Load all plugins from the plugins folder. This method must only be called - * once per instance. - */ - public void loadPlugins() - { - File dir = new File("plugins"); - dir.mkdir(); - - for (File file : dir.listFiles(new PatternFilenameFilter(".*\\.jar"))) - { - try - { - JarFile jar = new JarFile(file); - ZipEntry entry = jar.getEntry("plugin.yml"); - if (entry == null) - { - throw new InvalidPluginException("Jar does not contain a plugin.yml"); - } - - PluginDescription description; - try (InputStream is = jar.getInputStream(entry)) - { - description = PluginDescription.load(is); - } - URLClassLoader classloader = new URLClassLoader(new URL[] - { - file.toURI().toURL() - }, getClass().getClassLoader()); - Class clazz = Class.forName(description.getMain(), true, classloader); - Class subClazz = clazz.asSubclass(JavaPlugin.class); - JavaPlugin plugin = subClazz.getDeclaredConstructor().newInstance(); - - plugin.description = description; - plugin.onEnable(); - plugins.add(plugin); - - $().info("Loaded plugin: " + plugin.description.getName()); - } catch (Exception ex) - { - $().severe("Could not load plugin: " + file); - ex.printStackTrace(); - } - } - } - - @Override - public void onDisable() - { - for (JavaPlugin p : plugins) - { - p.onDisable(); - } - } - - @Override - public void onHandshake(LoginEvent event) - { - for (JavaPlugin p : plugins) - { - p.onHandshake(event); - } - } - - @Override - public void onLogin(LoginEvent event) - { - for (JavaPlugin p : plugins) - { - p.onLogin(event); - } - } - - @Override - public void onServerConnect(ServerConnectEvent event) - { - for (JavaPlugin p : plugins) - { - p.onServerConnect(event); - } - } - - @Override - public void onPluginMessage(PluginMessageEvent event) - { - for (JavaPlugin p : plugins) - { - p.onPluginMessage(event); - } - } - - @Override - public void onChat(ChatEvent event) - { - for (JavaPlugin p : plugins) - { - p.onChat(event); - } - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/LoginEvent.java b/src/main/java/net/md_5/bungee/plugin/LoginEvent.java deleted file mode 100644 index 16479c84..00000000 --- a/src/main/java/net/md_5/bungee/plugin/LoginEvent.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.md_5.bungee.plugin; - -import java.net.InetAddress; -import lombok.Data; - -/** - * Event called to represent a player logging in. - */ -@Data -public class LoginEvent implements Cancellable -{ - - /** - * Canceled state. - */ - private boolean cancelled; - /** - * Message to use when kicking if this event is canceled. - */ - private String cancelReason; - /** - * Username which the player wishes to use. - */ - private final String username; - /** - * IP address of the remote connection. - */ - private final InetAddress address; - /** - * Hostname which the user tried to connect to. - */ - private final String hostname; -} diff --git a/src/main/java/net/md_5/bungee/plugin/PluginDescription.java b/src/main/java/net/md_5/bungee/plugin/PluginDescription.java deleted file mode 100644 index 78cca603..00000000 --- a/src/main/java/net/md_5/bungee/plugin/PluginDescription.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.md_5.bungee.plugin; - -import java.io.InputStream; -import java.lang.reflect.Field; -import lombok.Data; -import org.yaml.snakeyaml.Yaml; - -/** - * File which contains information about a plugin, its authors, and how to load - * it. - */ -@Data -public class PluginDescription -{ - - private String name; - private String main; - private String version; - private String author; - - private PluginDescription() - { - } - - public static PluginDescription load(InputStream is) - { - PluginDescription ret = new Yaml().loadAs(is, PluginDescription.class); - if (ret == null) - { - throw new InvalidPluginException("Could not load plugin description file."); - } - - for (Field f : PluginDescription.class.getDeclaredFields()) - { - try - { - if (f.get(ret) == null) - { - throw new InvalidPluginException(f.getName() + " is not set properly in plugin description"); - } - } catch (IllegalArgumentException | IllegalAccessException ex) - { - } - } - - return ret; - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/PluginMessageEvent.java b/src/main/java/net/md_5/bungee/plugin/PluginMessageEvent.java deleted file mode 100644 index ad74ebdd..00000000 --- a/src/main/java/net/md_5/bungee/plugin/PluginMessageEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.md_5.bungee.plugin; - -import lombok.Data; -import net.md_5.bungee.UserConnection; - -/** - * Event called when a plugin message is sent to the client or server - */ -@Data -public class PluginMessageEvent implements Cancellable -{ - - /** - * Canceled state. - */ - private boolean cancelled; - /** - * Message to use when kicking if this event is canceled. - */ - private String cancelReason; - /** - * Whether this packet is destined for the server or the client - */ - private final Destination destination; - /** - * User in question - */ - private final UserConnection connection; - /** - * Tag specified for this plugin message. - */ - private String tag; - /** - * Data contained in this plugin message. - */ - private String data; - - /** - * An enum that signifies the destination for this packet - */ - public enum Destination - { - - SERVER, - CLIENT - } -} diff --git a/src/main/java/net/md_5/bungee/plugin/ServerConnectEvent.java b/src/main/java/net/md_5/bungee/plugin/ServerConnectEvent.java deleted file mode 100644 index 6a7339bb..00000000 --- a/src/main/java/net/md_5/bungee/plugin/ServerConnectEvent.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.md_5.bungee.plugin; - -import lombok.Data; -import net.md_5.bungee.UserConnection; - -/** - * Event called when the decision is made to decide which server to connect to. - */ -@Data -public class ServerConnectEvent -{ - - /** - * If the player currently has no server, this is true - */ - private final boolean firstTime; - /** - * Message to send just before the change. null for no message - */ - private String message; - /** - * User in question. - */ - private final UserConnection connection; - /** - * Name of the server they are connecting to. - */ - private final String server; - /** - * Name of the server which they will be forwarded to instead. - */ - private String newServer; -} diff --git a/src/main/java/net/md_5/bungee/tablist/GlobalPingTabList.java b/src/main/java/net/md_5/bungee/tablist/GlobalPingTabList.java deleted file mode 100644 index c8b39b5b..00000000 --- a/src/main/java/net/md_5/bungee/tablist/GlobalPingTabList.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.md_5.bungee.tablist; - -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.UserConnection; -import net.md_5.bungee.packet.PacketC9PlayerListItem; - -public class GlobalPingTabList extends GlobalTabList -{ - - public static final int PING_THRESHOLD = 20; - private Map lastPings = Collections.synchronizedMap(new WeakHashMap()); - - @Override - public void onPingChange(final UserConnection con, final int ping) - { - Integer lastPing = lastPings.get(con); - if (lastPing == null || (ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing)) - { - BungeeCord.instance.broadcast(new PacketC9PlayerListItem(con.tabListName, true, ping)); - lastPings.put(con, ping); - } - } -} diff --git a/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java b/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java deleted file mode 100644 index f97fd0e8..00000000 --- a/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.md_5.bungee.tablist; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.UserConnection; -import net.md_5.bungee.packet.PacketC9PlayerListItem; - -public class GlobalTabList implements TabListHandler -{ - - private Set sentPings = Collections.synchronizedSet(new HashSet()); - - @Override - public void onJoin(UserConnection con) - { - for (UserConnection c : BungeeCord.instance.connections.values()) - { - con.packetQueue.add(new PacketC9PlayerListItem(c.tabListName, true, c.getPing())); - } - } - - @Override - public void onServerChange(UserConnection con) - { - } - - @Override - public void onPingChange(final UserConnection con, final int ping) - { - if (!sentPings.contains(con)) - { - BungeeCord.instance.broadcast(new PacketC9PlayerListItem(con.tabListName, true, con.getPing())); - sentPings.add(con); - } - } - - @Override - public void onDisconnect(final UserConnection con) - { - BungeeCord.instance.broadcast(new PacketC9PlayerListItem(con.tabListName, false, 9999)); - sentPings.remove(con); - } - - @Override - public boolean onPacketC9(UserConnection con, PacketC9PlayerListItem packet) - { - return false; - } -} diff --git a/src/main/java/net/md_5/bungee/tablist/ServerUniqueTabList.java b/src/main/java/net/md_5/bungee/tablist/ServerUniqueTabList.java deleted file mode 100644 index e17f7ce4..00000000 --- a/src/main/java/net/md_5/bungee/tablist/ServerUniqueTabList.java +++ /dev/null @@ -1,68 +0,0 @@ -package net.md_5.bungee.tablist; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; -import net.md_5.bungee.UserConnection; -import net.md_5.bungee.packet.PacketC9PlayerListItem; - -public class ServerUniqueTabList implements TabListHandler -{ - - private Map> sentUsernames = Collections.synchronizedMap(new WeakHashMap>()); - - @Override - public void onJoin(UserConnection con) - { - } - - @Override - public void onServerChange(UserConnection con) - { - Set usernames = sentUsernames.get(con); - if (usernames != null) - { - synchronized (usernames) - { - for (String username : usernames) - { - con.packetQueue.add(new PacketC9PlayerListItem(username, false, 9999)); - } - usernames.clear(); - } - } - } - - @Override - public void onPingChange(UserConnection con, int ping) - { - } - - @Override - public void onDisconnect(UserConnection con) - { - } - - @Override - public boolean onPacketC9(final UserConnection con, final PacketC9PlayerListItem packet) - { - Set usernames = sentUsernames.get(con); - if (usernames == null) - { - usernames = new LinkedHashSet<>(); - sentUsernames.put(con, usernames); - } - - if (packet.online) - { - usernames.add(packet.username); - } else - { - usernames.remove(packet.username); - } - - return true; - } -} diff --git a/src/main/java/net/md_5/bungee/tablist/TabListHandler.java b/src/main/java/net/md_5/bungee/tablist/TabListHandler.java deleted file mode 100644 index b11d92e7..00000000 --- a/src/main/java/net/md_5/bungee/tablist/TabListHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.md_5.bungee.tablist; - -import net.md_5.bungee.UserConnection; -import net.md_5.bungee.packet.PacketC9PlayerListItem; - -public interface TabListHandler -{ - - public void onJoin(UserConnection con); - - public void onServerChange(UserConnection con); - - public void onPingChange(UserConnection con, int ping); - - public void onDisconnect(UserConnection con); - - public boolean onPacketC9(UserConnection con, PacketC9PlayerListItem packet); -}