diff --git a/.gitignore b/.gitignore index fd8f6bff5..aa52285df 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,4 @@ logs/ server-icon.png /bin/ run/ +plugins/ \ No newline at end of file diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java index a00f9c7d3..6a8310c72 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java @@ -19,7 +19,8 @@ import javax.tools.StandardLocation; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; -import java.util.*; +import java.util.Objects; +import java.util.Set; @SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"}) public class PluginAnnotationProcessor extends AbstractProcessor { diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java new file mode 100644 index 000000000..b16f933e4 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java @@ -0,0 +1,105 @@ +package com.velocitypowered.api.event.connection; + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.ChannelMessageSink; +import com.velocitypowered.api.proxy.messages.ChannelMessageSource; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Arrays; + +/** + * This event is fired when a plugin message is sent to the proxy, either from a client ({@link com.velocitypowered.api.proxy.Player}) + * or a server ({@link com.velocitypowered.api.proxy.ServerConnection}). + */ +public class PluginMessageEvent implements ResultedEvent { + private final ChannelMessageSource source; + private final ChannelMessageSink target; + private final ChannelIdentifier identifier; + private final byte[] data; + private ForwardResult result; + + public PluginMessageEvent(ChannelMessageSource source, ChannelMessageSink target, ChannelIdentifier identifier, byte[] data) { + this.source = Preconditions.checkNotNull(source, "source"); + this.target = Preconditions.checkNotNull(target, "target"); + this.identifier = Preconditions.checkNotNull(identifier, "identifier"); + this.data = Preconditions.checkNotNull(data, "data"); + this.result = ForwardResult.forward(); + } + + @Override + public ForwardResult getResult() { + return result; + } + + @Override + public void setResult(@NonNull ForwardResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public ChannelMessageSource getSource() { + return source; + } + + public ChannelMessageSink getTarget() { + return target; + } + + public ChannelIdentifier getIdentifier() { + return identifier; + } + + public byte[] getData() { + return Arrays.copyOf(data, data.length); + } + + public ByteArrayDataInput dataAsDataStream() { + return ByteStreams.newDataInput(data); + } + + @Override + public String toString() { + return "PluginMessageEvent{" + + "source=" + source + + ", target=" + target + + ", identifier=" + identifier + + ", data=" + Arrays.toString(data) + + ", result=" + result + + '}'; + } + + /** + * A result determining whether or not to forward this message on. + */ + public static class ForwardResult implements ResultedEvent.Result { + private static final ForwardResult ALLOWED = new ForwardResult(true); + private static final ForwardResult DENIED = new ForwardResult(false); + + private final boolean allowed; + + private ForwardResult(boolean b) { + this.allowed = b; + } + + @Override + public boolean isAllowed() { + return allowed; + } + + @Override + public String toString() { + return allowed ? "forward to sink" : "handled message at proxy"; + } + + public static ForwardResult forward() { + return ALLOWED; + } + + public static ForwardResult handled() { + return DENIED; + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java new file mode 100644 index 000000000..7ecc23bbc --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java @@ -0,0 +1,28 @@ +package com.velocitypowered.api.event.connection; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.Player; + +/** + * This event is fired once the player has been successfully authenticated and + * fully initialized and player will be connected to server after this event + */ +public class PostLoginEvent { + + private final Player player; + + public PostLoginEvent(Player player) { + this.player = Preconditions.checkNotNull(player, "player"); + } + + public Player getPlayer() { + return player; + } + + @Override + public String toString() { + return "PostLoginEvent{" + + "player=" + player + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java index ef8edfb9f..d5e817f49 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java @@ -3,12 +3,13 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.InboundConnection; - import net.kyori.text.Component; - +import net.kyori.text.serializer.ComponentSerializers; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; + /** * This event is fired when a player has initiated a connection with the proxy but before the proxy authenticates the * player with Mojang or before the player's proxy connection is fully established (for offline mode). @@ -52,44 +53,59 @@ public class PreLoginEvent implements ResultedEvent reason; + + private PreLoginComponentResult(Result result, @Nullable Component reason) { + this.result = result; + this.reason = Optional.ofNullable(reason); } - private PreLoginComponentResult(@Nullable Component reason) { - super(reason == null, reason); - // Don't care about this - this.onlineMode = false; + @Override + public boolean isAllowed() { + return result != Result.DISALLOWED; + } + + public Optional getReason() { + return reason; } public boolean isOnlineModeAllowed() { - return this.onlineMode; + return result == Result.FORCE_ONLINE; + } + + public boolean isForceOfflineMode() { + return result == Result.FORCE_OFFLINE; } @Override public String toString() { + if (isForceOfflineMode()) { + return "allowed with force offline mode"; + } if (isOnlineModeAllowed()) { return "allowed with online mode"; } - - return super.toString(); + if (isAllowed()) { + return "allowed"; + } + if (reason.isPresent()) { + return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get()); + } + return "denied"; } /** - * Returns a result indicating the connection will be allowed through the proxy. + * Returns a result indicating the connection will be allowed through + * the proxy. * @return the allowed result */ public static PreLoginComponentResult allowed() { @@ -97,23 +113,41 @@ public class PreLoginEvent implements ResultedEvent { private final Player player; - private final ServerInfo server; + private final RegisteredServer server; private final Component originalReason; private final boolean duringLogin; private ServerKickResult result; - public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) { + public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) { this.player = Preconditions.checkNotNull(player, "player"); this.server = Preconditions.checkNotNull(server, "server"); this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason"); @@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent { private final Player player; + private final RegisteredServer originalServer; private ServerResult result; - public ServerPreConnectEvent(Player player, ServerResult result) { + public ServerPreConnectEvent(Player player, RegisteredServer originalServer) { this.player = Preconditions.checkNotNull(player, "player"); - this.result = Preconditions.checkNotNull(result, "result"); + this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer"); + this.result = ServerResult.allowed(originalServer); } public Player getPlayer() { @@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent connect(); + CompletableFuture connect(); /** * Initiates the connection to the remote server without waiting for a result. Velocity will use generic error diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 821ed0587..8012e65dc 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,11 +1,11 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; -import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.api.proxy.player.PlayerSettings; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.MessagePosition; import java.util.List; import net.kyori.text.Component; @@ -65,10 +65,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage /** * Creates a new connection request so that the player can connect to another server. - * @param info the server to connect to + * @param server the server to connect to * @return a new connection request */ - ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); + ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server); /** * Gets a game profile properties of player diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java index b4c851c58..c6c75df8a 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -1,12 +1,13 @@ package com.velocitypowered.api.proxy; -import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.proxy.messages.ChannelRegistrar; -import com.velocitypowered.api.scheduler.Scheduler; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.api.scheduler.Scheduler; import java.net.InetSocketAddress; import java.util.Collection; @@ -45,23 +46,24 @@ public interface ProxyServer { int getPlayerCount(); /** - * Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive. + * Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive. * @param name the name of the server * @return the registered server, which may be empty */ - Optional getServerInfo(String name); + Optional getServer(String name); /** - * Retrieves all {@link ServerInfo}s registered with this proxy. + * Retrieves all {@link RegisteredServer}s registered with this proxy. * @return the servers registered with this proxy */ - Collection getAllServers(); + Collection getAllServers(); /** * Registers a server with this proxy. A server with this name should not already exist. * @param server the server to register + * @return the newly registered server */ - void registerServer(ServerInfo server); + RegisteredServer registerServer(ServerInfo server); /** * Unregisters this server from the proxy. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java index 5f12fb326..b800131c0 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; /** @@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi * Returns the server that this connection is connected to. * @return the server this connection is connected to */ + RegisteredServer getServer(); + + /** + * Returns the server info for this connection. + * @return the server info for this connection + */ ServerInfo getServerInfo(); /** diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java index 57ebe4ea6..37f02e539 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java @@ -8,6 +8,7 @@ public interface ChannelMessageSink { * Sends a plugin message to this target. * @param identifier the channel identifier to send the message on * @param data the data to send + * @return whether or not the message could be sent */ - void sendPluginMessage(ChannelIdentifier identifier, byte[] data); + boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java index 2d77988b5..84db8799d 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java @@ -1,16 +1,14 @@ package com.velocitypowered.api.proxy.messages; /** - * Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from - * the client or the server. + * Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on. */ public interface ChannelRegistrar { /** - * Registers the specified message handler to listen for plugin messages on the specified channels. - * @param handler the handler to register + * Registers the specified message identifiers to listen on for the * @param identifiers the channel identifiers to register */ - void register(MessageHandler handler, ChannelIdentifier... identifiers); + void register(ChannelIdentifier... identifiers); /** * Unregisters the handler for the specified channel. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java deleted file mode 100644 index 12256f432..000000000 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.velocitypowered.api.proxy.messages; - -/** - * Represents from "which side" of the proxy the plugin message came from. - */ -public enum ChannelSide { - /** - * The plugin message came from a server that a client was connected to. - */ - FROM_SERVER, - /** - * The plugin message came from the client. - */ - FROM_CLIENT -} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java deleted file mode 100644 index 70a7e5fa7..000000000 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.velocitypowered.api.proxy.messages; - -/** - * Represents a handler for handling plugin messages. - */ -public interface MessageHandler { - /** - * Handles an incoming plugin message. - * @param source the source of the plugin message - * @param side from where the plugin message originated - * @param identifier the channel on which the message was sent - * @param data the data inside the plugin message - * @return a {@link ForwardStatus} indicating whether or not to forward this plugin message on - */ - ForwardStatus handle(ChannelMessageSource source, ChannelSide side, ChannelIdentifier identifier, byte[] data); - - enum ForwardStatus { - /** - * Forwards this plugin message on to the client or server, depending on the {@link ChannelSide} it originated - * from. - */ - FORWARD, - /** - * Discard the plugin message and do not forward it on. - */ - HANDLED - } -} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java index b8f689324..1355ed239 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java @@ -10,7 +10,7 @@ import java.util.regex.Pattern; * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use. */ public final class MinecraftChannelIdentifier implements ChannelIdentifier { - private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+", Pattern.CASE_INSENSITIVE); + private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+"); private final String namespace; private final String name; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java new file mode 100644 index 000000000..b7bf9acb5 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java @@ -0,0 +1,30 @@ +package com.velocitypowered.api.proxy.server; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.messages.ChannelMessageSink; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +/** + * Represents a server that has been registered with the proxy. + */ +public interface RegisteredServer extends ChannelMessageSink { + /** + * Returns the {@link ServerInfo} for this server. + * @return the server info + */ + ServerInfo getServerInfo(); + + /** + * Returns a list of all the players currently connected to this server on this proxy. + * @return the players on this proxy + */ + Collection getPlayersConnected(); + + /** + * Attempts to ping the remote server and return the server list ping result. + * @return the server ping result from the server + */ + CompletableFuture ping(); +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index 252ad77c3..bf9a3f2ee 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -74,6 +74,7 @@ public class ServerPing { builder.favicon = favicon; builder.nullOutModinfo = modinfo == null; if (modinfo != null) { + builder.modType = modinfo.type; builder.mods.addAll(modinfo.modList); } return builder; @@ -91,6 +92,7 @@ public class ServerPing { private int onlinePlayers; private int maximumPlayers; private final List samplePlayers = new ArrayList<>(); + private String modType; private final List mods = new ArrayList<>(); private Component description; private Favicon favicon; @@ -121,6 +123,11 @@ public class ServerPing { return this; } + public Builder modType(String modType) { + this.modType = Preconditions.checkNotNull(modType, "modType"); + return this; + } + public Builder mods(Mod... mods) { this.mods.addAll(Arrays.asList(mods)); return this; @@ -158,7 +165,7 @@ public class ServerPing { public ServerPing build() { return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon, - nullOutModinfo ? null : new Modinfo(mods)); + nullOutModinfo ? null : new Modinfo(modType, mods)); } public Version getVersion() { @@ -185,6 +192,10 @@ public class ServerPing { return favicon; } + public String getModType() { + return modType; + } + public List getMods() { return mods; } @@ -196,6 +207,7 @@ public class ServerPing { ", onlinePlayers=" + onlinePlayers + ", maximumPlayers=" + maximumPlayers + ", samplePlayers=" + samplePlayers + + ", modType=" + modType + ", mods=" + mods + ", description=" + description + ", favicon=" + favicon + @@ -291,14 +303,23 @@ public class ServerPing { } public static class Modinfo { - public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of()); + public static final Modinfo DEFAULT = new Modinfo("FML", ImmutableList.of()); - private final String type = "FML"; + private final String type; private final List modList; - public Modinfo(List modList) { + public Modinfo(String type, List modList) { + this.type = Preconditions.checkNotNull(type, "type"); this.modList = ImmutableList.copyOf(modList); } + + public String getType() { + return type; + } + + public List getMods() { + return modList; + } } public static class Mod { diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java index fd652d1c6..2854a664c 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java @@ -24,7 +24,7 @@ public interface Scheduler { * @param unit the unit of time for {@code time} * @return this builder, for chaining */ - TaskBuilder delay(int time, TimeUnit unit); + TaskBuilder delay(long time, TimeUnit unit); /** * Specifies that the task should continue running after waiting for the specified amount, until it is cancelled. @@ -32,7 +32,7 @@ public interface Scheduler { * @param unit the unit of time for {@code time} * @return this builder, for chaining */ - TaskBuilder repeat(int time, TimeUnit unit); + TaskBuilder repeat(long time, TimeUnit unit); /** * Clears the delay on this task. diff --git a/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java b/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java deleted file mode 100644 index a21b97ffd..000000000 --- a/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.velocitypowered.api.util; - -import com.google.common.base.Preconditions; -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.regex.Pattern; - -/** - * LegacyChatColorUtils contains utilities for handling legacy Minecraft color codes. Generally, you should prefer - * JSON-based components, but for convenience Velocity provides a limited set of tools to handle Minecraft color codes. - */ -public class LegacyChatColorUtils { - private LegacyChatColorUtils() { - throw new AssertionError(); - } - - /** - * Represents the legacy Minecraft format character, the section symbol. - */ - public static final char FORMAT_CHAR = '\u00a7'; - - /** - * Translates a string with Minecraft color codes prefixed with a different character than the section symbol into - * a string that uses the section symbol. - * @param originalChar the char the color codes are prefixed by - * @param text the text to translate - * @return the translated text - */ - public static String translate(char originalChar, @NonNull String text) { - Preconditions.checkNotNull(text, "text"); - char[] textChars = text.toCharArray(); - int foundSectionIdx = -1; - for (int i = 0; i < textChars.length; i++) { - char textChar = textChars[i]; - if (textChar == originalChar) { - foundSectionIdx = i; - continue; - } - - if (foundSectionIdx >= 0) { - textChar = Character.toLowerCase(textChar); - if ((textChar >= 'a' && textChar <= 'f') || (textChar >= '0' && textChar <= '9') || - (textChar >= 'l' && textChar <= 'o' || textChar == 'r')) { - textChars[foundSectionIdx] = FORMAT_CHAR; - } - foundSectionIdx = -1; - } - } - return new String(textChars); - } - - /** - * A regex that matches all Minecraft color codes and removes them. - */ - private static final Pattern CHAT_COLOR_MATCHER = Pattern.compile("(?i)" + Character.toString(FORMAT_CHAR) + "[0-9A-FL-OR]"); - - /** - * Removes all Minecraft color codes from the string. - * @param text the text to remove color codes from - * @return a new String without Minecraft color codes - */ - public static String removeFormatting(@NonNull String text) { - Preconditions.checkNotNull(text, "text"); - return CHAT_COLOR_MATCHER.matcher(text).replaceAll(""); - } -} diff --git a/api/src/test/java/com/velocitypowered/api/util/LegacyChatColorUtilsTest.java b/api/src/test/java/com/velocitypowered/api/util/LegacyChatColorUtilsTest.java deleted file mode 100644 index b98b7027b..000000000 --- a/api/src/test/java/com/velocitypowered/api/util/LegacyChatColorUtilsTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.velocitypowered.api.util; - -import com.velocitypowered.api.util.LegacyChatColorUtils; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class LegacyChatColorUtilsTest { - private static final String NON_FORMATTED = "Velocity"; - private static final String FORMATTED = "\u00a7cVelocity"; - private static final String FORMATTED_MULTIPLE = "\u00a7c\u00a7lVelocity"; - private static final String FORMATTED_MULTIPLE_VARIED = "\u00a7c\u00a7lVelo\u00a7a\u00a7mcity"; - private static final String INVALID = "\u00a7gVelocity"; - private static final String RAW_SECTION = "\u00a7"; - - @Test - void removeFormattingNonFormatted() { - assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(NON_FORMATTED)); - } - - @Test - void removeFormattingFormatted() { - assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED)); - } - - @Test - void removeFormattingFormattedMultiple() { - assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE)); - } - - @Test - void removeFormattingFormattedMultipleVaried() { - assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE_VARIED)); - } - - @Test - void removeFormattingInvalidFormat() { - assertEquals(INVALID, LegacyChatColorUtils.removeFormatting(INVALID)); - } - - @Test - void removeFormattingRawSection() { - assertEquals(RAW_SECTION, LegacyChatColorUtils.removeFormatting(RAW_SECTION)); - } - - @Test - void translate() { - assertEquals(FORMATTED, LegacyChatColorUtils.translate('&', "&cVelocity")); - } - - @Test - void translateMultiple() { - assertEquals(FORMATTED_MULTIPLE, LegacyChatColorUtils.translate('&', "&c&lVelocity")); - assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('&', "&c&lVelo&a&mcity")); - } - - @Test - void translateDifferentChar() { - assertEquals(FORMATTED, LegacyChatColorUtils.translate('$', "$cVelocity")); - assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('$', "$c$lVelo$a$mcity")); - } -} \ No newline at end of file diff --git a/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java index 2f07378c8..4b84516da 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java +++ b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java @@ -8,7 +8,7 @@ import java.util.function.Supplier; public class NativeCodeLoader implements Supplier { private final List> variants; - private Variant selected; + private volatile Variant selected; public NativeCodeLoader(List> variants) { this.variants = ImmutableList.copyOf(variants); @@ -16,36 +16,41 @@ public class NativeCodeLoader implements Supplier { @Override public T get() { - if (selected == null) { - selected = select(); - } - return selected.object; + return tryLoad().object; } - private Variant select() { - for (Variant variant : variants) { - T got = variant.get(); - if (got == null) { - continue; - } - return variant; + private Variant tryLoad() { + if (selected != null) { + return selected; + } + + synchronized (this) { + if (selected != null) { + return selected; + } + + for (Variant variant : variants) { + T got = variant.get(); + if (got == null) { + continue; + } + selected = variant; + return selected; + } + throw new IllegalArgumentException("Can't find any suitable variants"); } - throw new IllegalArgumentException("Can't find any suitable variants"); } public String getLoadedVariant() { - if (selected == null) { - selected = select(); - } - return selected.name; + return tryLoad().name; } static class Variant { - private boolean available; + private volatile boolean available; private final Runnable setup; private final String name; private final T object; - private boolean hasBeenSetup = false; + private volatile boolean hasBeenSetup = false; Variant(BooleanSupplier available, Runnable setup, String name, T object) { this.available = available.getAsBoolean(); @@ -54,27 +59,34 @@ public class NativeCodeLoader implements Supplier { this.object = object; } - private void setup() { - if (available && !hasBeenSetup) { - try { - setup.run(); - hasBeenSetup = true; - } catch (Exception e) { - available = false; + public T get() { + if (!available) { + return null; + } + + // Make sure setup happens only once + if (!hasBeenSetup) { + synchronized (this) { + // We change availability if need be below, may as well check it again here for sanity. + if (!available) { + return null; + } + + // Okay, now try the setup if we haven't done so yet. + if (!hasBeenSetup) { + try { + setup.run(); + hasBeenSetup = true; + return object; + } catch (Exception e) { + available = false; + return null; + } + } } } - } - public T get() { - if (!hasBeenSetup) { - setup(); - } - - if (available) { - return object; - } - - return null; + return object; } } diff --git a/native/src/main/java/com/velocitypowered/natives/util/Natives.java b/native/src/main/java/com/velocitypowered/natives/util/Natives.java index 9c135843d..5c22ae752 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -3,10 +3,8 @@ package com.velocitypowered.natives.util; import com.google.common.collect.ImmutableList; import com.velocitypowered.natives.compression.JavaVelocityCompressor; import com.velocitypowered.natives.compression.NativeVelocityCompressor; -import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.encryption.JavaVelocityCipher; -import com.velocitypowered.natives.encryption.NativeVelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipherFactory; import java.io.IOException; diff --git a/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java b/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java index 93136b8df..363c80442 100644 --- a/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java +++ b/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; import java.util.Random; -import java.util.function.Supplier; import java.util.zip.DataFormatException; import java.util.zip.Deflater; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index d23709111..7eaf8eaf3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -8,29 +8,30 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.api.util.Favicon; -import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; -import com.velocitypowered.proxy.network.ConnectionManager; +import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.command.ServerCommand; import com.velocitypowered.proxy.command.ShutdownCommand; import com.velocitypowered.proxy.command.VelocityCommand; -import com.velocitypowered.proxy.config.VelocityConfiguration; -import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.network.http.NettyHttpClient; import com.velocitypowered.proxy.command.VelocityCommandManager; import com.velocitypowered.proxy.config.AnnotatedConfig; +import com.velocitypowered.proxy.config.VelocityConfiguration; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.messages.VelocityChannelRegistrar; +import com.velocitypowered.proxy.network.ConnectionManager; +import com.velocitypowered.proxy.network.http.NettyHttpClient; import com.velocitypowered.proxy.plugin.VelocityEventManager; -import com.velocitypowered.proxy.protocol.util.FaviconSerializer; import com.velocitypowered.proxy.plugin.VelocityPluginManager; +import com.velocitypowered.proxy.protocol.util.FaviconSerializer; import com.velocitypowered.proxy.scheduler.VelocityScheduler; +import com.velocitypowered.proxy.server.ServerMap; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.Ratelimiter; -import com.velocitypowered.proxy.util.ServerMap; import io.netty.bootstrap.Bootstrap; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -61,7 +62,7 @@ public class VelocityServer implements ProxyServer { private VelocityConfiguration configuration; private NettyHttpClient httpClient; private KeyPair serverKeyPair; - private final ServerMap servers = new ServerMap(); + private final ServerMap servers = new ServerMap(this); private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private boolean shutdown = false; @@ -263,19 +264,19 @@ public class VelocityServer implements ProxyServer { } @Override - public Optional getServerInfo(String name) { + public Optional getServer(String name) { Preconditions.checkNotNull(name, "name"); return servers.getServer(name); } @Override - public Collection getAllServers() { + public Collection getAllServers() { return servers.getAllServers(); } @Override - public void registerServer(ServerInfo server) { - servers.register(server); + public RegisteredServer registerServer(ServerInfo server) { + return servers.register(server); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java index b42a9ed26..2926bb038 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -6,6 +6,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; @@ -34,7 +35,7 @@ public class ServerCommand implements Command { if (args.length == 1) { // Trying to connect to a server. String serverName = args[0]; - Optional toConnect = server.getServerInfo(serverName); + Optional toConnect = server.getServer(serverName); if (!toConnect.isPresent()) { player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); return; @@ -48,17 +49,19 @@ public class ServerCommand implements Command { // Assemble the list of servers as components TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW); - List infos = ImmutableList.copyOf(server.getAllServers()); + List infos = ImmutableList.copyOf(server.getAllServers()); for (int i = 0; i < infos.size(); i++) { - ServerInfo serverInfo = infos.get(i); - TextComponent infoComponent = TextComponent.of(serverInfo.getName()); - if (serverInfo.getName().equals(currentServer)) { + RegisteredServer rs = infos.get(i); + TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName()); + String playersText = rs.getPlayersConnected().size() + " player(s) online"; + if (rs.getServerInfo().getName().equals(currentServer)) { infoComponent = infoComponent.color(TextColor.GREEN) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Currently connected to this server"))); + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("Currently connected to this server\n" + playersText))); } else { infoComponent = infoComponent.color(TextColor.GRAY) - .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName())) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server"))); + .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName())) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText))); } serverListBuilder.append(infoComponent); if (i != infos.size() - 1) { @@ -74,11 +77,11 @@ public class ServerCommand implements Command { public List suggest(CommandSource source, String[] currentArgs) { if (currentArgs.length == 0) { return server.getAllServers().stream() - .map(ServerInfo::getName) + .map(rs -> rs.getServerInfo().getName()) .collect(Collectors.toList()); } else if (currentArgs.length == 1) { return server.getAllServers().stream() - .map(ServerInfo::getName) + .map(rs -> rs.getServerInfo().getName()) .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) .collect(Collectors.toList()); } else { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 38c3759ea..7793bf075 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -1,10 +1,9 @@ package com.velocitypowered.proxy.command; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.Command; -import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandSource; import java.util.*; import java.util.stream.Collectors; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index 32f9a4880..99a417846 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -1,5 +1,8 @@ package com.velocitypowered.proxy.config; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.io.File; import java.io.IOException; import java.lang.annotation.ElementType; @@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; -import java.nio.file.AtomicMoveNotSupportedException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; +import java.nio.file.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Only for simple configs diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 470e064f8..303dbcd3f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -5,10 +5,9 @@ import com.google.common.collect.ImmutableMap; import com.moandjiezana.toml.Toml; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; -import com.velocitypowered.api.util.LegacyChatColorUtils; -import io.netty.buffer.ByteBufUtil; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; +import org.apache.logging.log4j.Logger; import java.io.IOException; import java.io.Reader; @@ -17,12 +16,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import org.apache.logging.log4j.Logger; +import java.util.*; public class VelocityConfiguration extends AnnotatedConfig { @@ -273,6 +267,14 @@ public class VelocityConfiguration extends AnnotatedConfig { return announceForge; } + public int getConnectTimeout() { + return advanced.getConnectionTimeout(); + } + + public int getReadTimeout() { + return advanced.getReadTimeout(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -420,21 +422,23 @@ public class VelocityConfiguration extends AnnotatedConfig { "Disable by setting to 0"}) @ConfigKey("login-ratelimit") private int loginRatelimit = 3000; + @Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."}) + @ConfigKey("connection-timeout") + private int connectionTimeout = 5000; + @Comment({"Specify a read timeout for connections here. The default is 30 seconds."}) + @ConfigKey("read-timeout") + private int readTimeout = 30000; private Advanced() { } - private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) { - this.compressionThreshold = compressionThreshold; - this.compressionLevel = compressionLevel; - this.loginRatelimit = loginRatelimit; - } - private Advanced(Toml toml) { if (toml != null) { this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); + this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue(); + this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); } } @@ -442,33 +446,31 @@ public class VelocityConfiguration extends AnnotatedConfig { return compressionThreshold; } - public void setCompressionThreshold(int compressionThreshold) { - this.compressionThreshold = compressionThreshold; - } - public int getCompressionLevel() { return compressionLevel; } - public void setCompressionLevel(int compressionLevel) { - this.compressionLevel = compressionLevel; - } - public int getLoginRatelimit() { return loginRatelimit; } - public void setLoginRatelimit(int loginRatelimit) { - this.loginRatelimit = loginRatelimit; + public int getConnectionTimeout() { + return connectionTimeout; + } + + public int getReadTimeout() { + return readTimeout; } @Override public String toString() { - return "Advanced{" - + "compressionThreshold=" + compressionThreshold - + ", compressionLevel=" + compressionLevel - + ", loginRatelimit=" + loginRatelimit - + '}'; + return "Advanced{" + + "compressionThreshold=" + compressionThreshold + + ", compressionLevel=" + compressionLevel + + ", loginRatelimit=" + loginRatelimit + + ", connectionTimeout=" + connectionTimeout + + ", readTimeout=" + readTimeout + + '}'; } } @@ -500,18 +502,10 @@ public class VelocityConfiguration extends AnnotatedConfig { return queryEnabled; } - public void setQueryEnabled(boolean queryEnabled) { - this.queryEnabled = queryEnabled; - } - public int getQueryPort() { return queryPort; } - public void setQueryPort(int queryPort) { - this.queryPort = queryPort; - } - @Override public String toString() { return "Query{" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index b4a68dfbf..6f57fd2ba 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection; import com.google.common.base.Preconditions; import com.velocitypowered.natives.compression.VelocityCompressor; +import com.velocitypowered.natives.encryption.VelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.natives.encryption.VelocityCipher; import com.velocitypowered.proxy.protocol.netty.*; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; @@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; - import java.security.GeneralSecurityException; -import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER; -import static com.velocitypowered.proxy.network.Connections.CIPHER_ENCODER; -import static com.velocitypowered.proxy.network.Connections.COMPRESSION_DECODER; -import static com.velocitypowered.proxy.network.Connections.COMPRESSION_ENCODER; -import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; -import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; -import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; -import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; +import static com.velocitypowered.proxy.network.Connections.*; /** * A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft @@ -99,8 +91,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { if (association != null) { logger.error("{}: exception encountered", association, cause); - } else { - logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause); } ctx.close(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java index b6956336b..b524b31e1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java @@ -8,6 +8,8 @@ public class VelocityConstants { public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; + public static final String FORGE_LEGACY_CHANNEL = "FML"; + public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP"; public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 }; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 1a011a2e8..fea65c6a9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -1,15 +1,16 @@ package com.velocitypowered.proxy.connection.backend; +import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; -import com.velocitypowered.api.proxy.messages.ChannelSide; -import com.velocitypowered.api.proxy.messages.MessageHandler; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; +import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.packet.*; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; @@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void activated() { - server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo())); + server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer())); + connection.getServer().addPlayer(connection.getPlayer()); } @Override @@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { if (!connection.getPlayer().isActive()) { // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // errors. - connection.getMinecraftConnection().close(); + connection.disconnect(); return; } @@ -44,7 +46,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { connection.getPlayer().getConnection().write(packet); } else if (packet instanceof Disconnect) { Disconnect original = (Disconnect) packet; - connection.getPlayer().handleConnectionException(connection.getServerInfo(), original); + connection.disconnect(); + connection.getPlayer().handleConnectionException(connection.getServer(), original); } else if (packet instanceof JoinGame) { playerHandler.handleBackendJoinGame((JoinGame) packet); } else if (packet instanceof BossBar) { @@ -83,10 +86,17 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return; } - MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection, - ChannelSide.FROM_SERVER, pm); - if (status == MessageHandler.ForwardStatus.FORWARD) { + ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel()); + if (id == null) { connection.getPlayer().getConnection().write(pm); + } else { + PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), id, pm.getData()); + server.getEventManager().fire(event) + .thenAcceptAsync(pme -> { + if (pme.getResult().isAllowed()) { + connection.getPlayer().getConnection().write(pm); + } + }, connection.getMinecraftConnection().getChannel().eventLoop()); } } else if (connection.hasCompletedJoin()) { // Just forward the packet on. We don't have anything to handle at this time. @@ -99,7 +109,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { if (!connection.getPlayer().isActive()) { // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // errors. - connection.getMinecraftConnection().close(); + connection.disconnect(); return; } @@ -110,7 +120,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void exception(Throwable throwable) { - connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable); + connection.getPlayer().handleConnectionException(connection.getServer(), throwable); + } + + public VelocityServer getServer() { + return server; + } + + @Override + public void disconnected() { + connection.getServer().removePlayer(connection.getPlayer()); + if (!connection.isGracefulDisconnect()) { + connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create( + ConnectionMessages.UNEXPECTED_DISCONNECT)); + } } private boolean canForwardPluginMessage(PluginMessage message) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index c65f1745e..5fb5341b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -1,18 +1,18 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; +import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.VelocityConfiguration; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; -import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.*; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.kyori.text.TextComponent; @@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (existingConnection == null) { // Strap on the play session handler connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer())); - - // This is for legacy Forge servers - during first connection the FML handshake will transition to complete regardless - // Thus, we need to ensure that a reset packet is ALWAYS sent on first switch. - // - // The call will handle if the player is not a Forge player appropriately. - connection.getPlayer().getConnection().setCanSendLegacyFMLResetPacket(true); } else { // The previous server connection should become obsolete. // Before we remove it, if the server we are departing is modded, we must always reset the client state. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index ceb53b9e6..80210e33a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -4,9 +4,14 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; @@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ServerLogin; -import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.api.proxy.server.ServerInfo; -import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import io.netty.channel.*; +import com.velocitypowered.proxy.server.VelocityRegisteredServer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelInitializer; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.util.AttributeKey; @@ -27,27 +31,22 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import static com.velocitypowered.proxy.VelocityServer.GSON; -import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; -import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; -import static com.velocitypowered.proxy.network.Connections.HANDLER; -import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; -import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; -import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; -import static com.velocitypowered.proxy.network.Connections.SERVER_READ_TIMEOUT_SECONDS; +import static com.velocitypowered.proxy.network.Connections.*; public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection { static final AttributeKey> CONNECTION_NOTIFIER = AttributeKey.newInstance("connection-notification-result"); - private final ServerInfo serverInfo; + private final VelocityRegisteredServer registeredServer; private final ConnectedPlayer proxyPlayer; private final VelocityServer server; private MinecraftConnection minecraftConnection; private boolean legacyForge = false; private boolean hasCompletedJoin = false; + private boolean gracefulDisconnect = false; - public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { - this.serverInfo = target; + public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) { + this.registeredServer = registeredServer; this.proxyPlayer = proxyPlayer; this.server = server; } @@ -55,12 +54,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, public CompletableFuture connect() { CompletableFuture result = new CompletableFuture<>(); server.initializeGenericBootstrap() - .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline() - .addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) + .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS)) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) @@ -73,7 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, ch.pipeline().addLast(HANDLER, connection); } }) - .connect(serverInfo.getAddress()) + .connect(registeredServer.getServerInfo().getAddress()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { @@ -95,7 +93,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, // separated by \0 (the null byte). In order, you send the original host, the player's IP, their // UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang). - return serverInfo.getAddress().getHostString() + "\0" + + return registeredServer.getServerInfo().getAddress().getHostString() + "\0" + proxyPlayer.getRemoteAddress().getHostString() + "\0" + proxyPlayer.getProfile().getId() + "\0" + GSON.toJson(proxyPlayer.getProfile().getProperties()); @@ -113,9 +111,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, } else if (proxyPlayer.getConnection().isLegacyForge()) { handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); } else { - handshake.setServerAddress(serverInfo.getAddress().getHostString()); + handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); } - handshake.setPort(serverInfo.getAddress().getPort()); + handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); minecraftConnection.write(handshake); int protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); @@ -127,12 +125,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, minecraftConnection.write(login); } + public void writeIfJoined(PluginMessage message) { + if (hasCompletedJoin) { + minecraftConnection.write(message); + } + } + public MinecraftConnection getMinecraftConnection() { return minecraftConnection; } + @Override + public VelocityRegisteredServer getServer() { + return registeredServer; + } + + @Override public ServerInfo getServerInfo() { - return serverInfo; + return registeredServer.getServerInfo(); } @Override @@ -141,23 +151,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, } public void disconnect() { - minecraftConnection.close(); - minecraftConnection = null; + if (minecraftConnection != null) { + minecraftConnection.close(); + minecraftConnection = null; + gracefulDisconnect = true; + } } @Override public String toString() { - return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); + return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName(); } @Override - public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); PluginMessage message = new PluginMessage(); message.setChannel(identifier.getId()); message.setData(data); minecraftConnection.write(message); + return true; } public boolean isLegacyForge() { @@ -175,4 +189,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, public void setHasCompletedJoin(boolean hasCompletedJoin) { this.hasCompletedJoin = hasCompletedJoin; } + + public boolean isGracefulDisconnect() { + return gracefulDisconnect; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index b7cb0f91e..91d2d64ac 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -1,14 +1,15 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.proxy.messages.ChannelSide; -import com.velocitypowered.api.proxy.messages.MessageHandler; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.packet.*; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.util.ThrowableUtils; import io.netty.buffer.ByteBuf; @@ -33,6 +34,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private boolean spawned = false; private final List serverBossBars = new ArrayList<>(); private final Set clientPluginMsgChannels = new HashSet<>(); + private final Queue loginPluginMessages = new ArrayDeque<>(); private final VelocityServer server; public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { @@ -53,6 +55,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { + VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection == null) { + // No server connection yet, probably transitioning. + return; + } + if (packet instanceof KeepAlive) { KeepAlive keepAlive = (KeepAlive) packet; if (keepAlive.getRandomId() != lastPingID) { @@ -62,7 +70,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } player.setPing(System.currentTimeMillis() - lastPingSent); resetPingData(); - player.getConnectedServer().getMinecraftConnection().write(packet); + serverConnection.getMinecraftConnection().write(packet); return; } @@ -110,7 +118,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getConnection().write(response); } else { - player.getConnectedServer().getMinecraftConnection().write(packet); + serverConnection.getMinecraftConnection().write(packet); } } catch (Exception e) { logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e); @@ -125,15 +133,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } // If we don't want to handle this packet, just forward it on. - if (player.getConnectedServer().hasCompletedJoin()) { - player.getConnectedServer().getMinecraftConnection().write(packet); + if (serverConnection.hasCompletedJoin()) { + serverConnection.getMinecraftConnection().write(packet); } } @Override public void handleUnknown(ByteBuf buf) { - if (player.getConnectedServer().hasCompletedJoin()) { - player.getConnectedServer().getMinecraftConnection().write(buf.retain()); + VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection == null) { + // No server connection yet, probably transitioning. + return; + } + + if (serverConnection.hasCompletedJoin()) { + serverConnection.getMinecraftConnection().write(buf.retain()); } } @@ -162,11 +176,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } public void handleBackendJoinGame(JoinGame joinGame) { - resetPingData(); // reset ping data; + resetPingData(); // reset ping data if (!spawned) { - // nothing special to do here + // Nothing special to do with regards to spawning the player spawned = true; player.getConnection().delayedWrite(joinGame); + + // We have something special to do for legacy Forge servers - during first connection the FML handshake + // will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on + // first switch. + // + // As we know that calling this branch only happens on first join, we set that if we are a Forge + // client that we must reset on the next switch. + // + // The call will handle if the player is not a Forge player appropriately. + player.getConnection().setCanSendLegacyFMLResetPacket(true); } else { // Ah, this is the meat and potatoes of the whole venture! // @@ -210,6 +234,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { channel, toRegister)); } + // If we had plugin messages queued during login/FML handshake, send them now. + PluginMessage pm; + while ((pm = loginPluginMessages.poll()) != null) { + player.getConnectedServer().getMinecraftConnection().delayedWrite(pm); + } + // Flush everything player.getConnection().flush(); player.getConnectedServer().getMinecraftConnection().flush(); @@ -234,8 +264,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return serverBossBars; } - public void handleClientPluginMessage(PluginMessage packet) { - if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) { + private void handleClientPluginMessage(PluginMessage packet) { + if (PluginMessageUtil.isMCRegister(packet)) { List actuallyRegistered = new ArrayList<>(); List channels = PluginMessageUtil.getChannels(packet); for (String channel : channels) { @@ -252,31 +282,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered); player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket); } - - return; - } - - if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) { + } else if (PluginMessageUtil.isMCUnregister(packet)) { List channels = PluginMessageUtil.getChannels(packet); clientPluginMsgChannels.removeAll(channels); - } - - if (PluginMessageUtil.isMCBrand(packet)) { + player.getConnectedServer().getMinecraftConnection().write(packet); + } else if (PluginMessageUtil.isMCBrand(packet)) { player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet)); - return; - } - - if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) { - // Ensure that the messages are forwarded - player.getConnectedServer().getMinecraftConnection().write(packet); - return; - } - - MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player, - ChannelSide.FROM_CLIENT, packet); - if (status == MessageHandler.ForwardStatus.FORWARD) { - // We're going to forward on the original packet. - player.getConnectedServer().getMinecraftConnection().write(packet); + } else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) { + if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { + // Always forward the FML handshake to the remote server. + player.getConnectedServer().getMinecraftConnection().write(packet); + } else { + // The client is trying to send messages too early. This is primarily caused by mods, but it's further + // aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to + // be sent once the JoinGame packet has been received by the proxy. + loginPluginMessages.add(packet); + } + } else { + ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); + if (id == null) { + player.getConnectedServer().getMinecraftConnection().write(packet); + } else { + PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData()); + server.getEventManager().fire(event) + .thenAcceptAsync(pme -> { + if (pme.getResult().isAllowed()) { + player.getConnectedServer().getMinecraftConnection().write(packet); + } + }, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop()); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java index 4977c5df9..235915f24 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java @@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.SkinParts; import com.velocitypowered.proxy.protocol.packet.ClientSettings; + import java.util.Locale; public class ClientSettingsWrapper implements PlayerSettings { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 1fb951dea..f6800ae90 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -7,28 +7,24 @@ import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionProvider; -import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.player.PlayerSettings; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.MessagePosition; -import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.VelocityConstants; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; -import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.proxy.protocol.packet.Chat; -import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.protocol.packet.ClientSettings; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.util.ThrowableUtils; -import com.velocitypowered.api.proxy.server.ServerInfo; -import com.velocitypowered.proxy.protocol.packet.Disconnect; -import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; - import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; @@ -159,8 +155,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) { - return new ConnectionRequestBuilderImpl(info); + public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) { + return new ConnectionRequestBuilderImpl(server); } @Override @@ -195,52 +191,57 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return connectedServer; } - public void handleConnectionException(ServerInfo info, Throwable throwable) { + public void handleConnectionException(RegisteredServer server, Throwable throwable) { String error = ThrowableUtils.briefDescription(throwable); String userMessage; - if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { - userMessage = "Exception in server " + info.getName(); + if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { + userMessage = "Exception in server " + server.getServerInfo().getName(); } else { - logger.error("{}: unable to connect to server {}", this, info.getName(), throwable); - userMessage = "Exception connecting to server " + info.getName(); + logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable); + userMessage = "Exception connecting to server " + server.getServerInfo().getName(); } - handleConnectionException(info, null, TextComponent.builder() + handleConnectionException(server, null, TextComponent.builder() .content(userMessage + ": ") .color(TextColor.RED) .append(TextComponent.of(error, TextColor.WHITE)) .build()); } - public void handleConnectionException(ServerInfo info, Disconnect disconnect) { + public void handleConnectionException(RegisteredServer server, Disconnect disconnect) { Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason()); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); - if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { - logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason); + if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { + logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason); + handleConnectionException(server, disconnectReason, TextComponent.builder() + .content("Kicked from " + server.getServerInfo().getName() + ": ") + .color(TextColor.RED) + .append(disconnectReason) + .build()); } else { - logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason); + logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason); + handleConnectionException(server, disconnectReason, TextComponent.builder() + .content("Unable to connect to " + server.getServerInfo().getName() + ": ") + .color(TextColor.RED) + .append(disconnectReason) + .build()); } - handleConnectionException(info, disconnectReason, TextComponent.builder() - .content("Unable to connect to " + info.getName() + ": ") - .color(TextColor.RED) - .append(disconnectReason) - .build()); } - private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) { - boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);; + private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) { + boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo()); connectionInFlight = null; if (connectedServer == null) { // The player isn't yet connected to a server. - Optional nextServer = getNextServerToTry(); + Optional nextServer = getNextServerToTry(); if (nextServer.isPresent()) { createConnectionRequest(nextServer.get()).fireAndForget(); } else { connection.closeWith(Disconnect.create(friendlyReason)); } - } else if (connectedServer.getServerInfo().equals(info)) { + } else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) { // Already connected to the server being disconnected from. if (kickReason != null) { - server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason)) + server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason)) .thenAcceptAsync(event -> { if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) { KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult(); @@ -261,7 +262,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } } - Optional getNextServerToTry() { + Optional getNextServerToTry() { List serversToTry = server.getConfiguration().getAttemptConnectionOrder(); if (tryIndex >= serversToTry.size()) { return Optional.empty(); @@ -272,21 +273,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return server.getServers().getServer(toTryName); } - private CompletableFuture connect(ConnectionRequestBuilderImpl request) { + private Optional checkServer(RegisteredServer server) { + Preconditions.checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server."); if (connectionInFlight != null) { - return CompletableFuture.completedFuture( - ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS) - ); + return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS); } + if (connectedServer != null && connectedServer.getServer().equals(server)) { + return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED); + } + return Optional.empty(); + } - if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) { - return CompletableFuture.completedFuture( - ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED) - ); + private CompletableFuture connect(ConnectionRequestBuilderImpl request) { + Optional initialCheck = checkServer(request.getServer()); + if (initialCheck.isPresent()) { + return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get())); } // Otherwise, initiate the connection. - ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer())); + ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer()); return server.getEventManager().fire(event) .thenCompose((newEvent) -> { if (!newEvent.getResult().isAllowed()) { @@ -295,7 +300,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { ); } - return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect(); + RegisteredServer rs = newEvent.getResult().getServer().get(); + Optional lastCheck = checkServer(rs); + if (lastCheck.isPresent()) { + return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get())); + } + return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect(); }); } @@ -341,25 +351,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); PluginMessage message = new PluginMessage(); message.setChannel(identifier.getId()); message.setData(data); connection.write(message); + return true; } private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { - private final ServerInfo info; + private final RegisteredServer server; - ConnectionRequestBuilderImpl(ServerInfo info) { - this.info = Preconditions.checkNotNull(info, "info"); + ConnectionRequestBuilderImpl(RegisteredServer server) { + this.server = Preconditions.checkNotNull(server, "info"); } @Override - public ServerInfo getServer() { - return info; + public RegisteredServer getServer() { + return server; } @Override @@ -372,7 +383,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { connect() .whenCompleteAsync((status, throwable) -> { if (throwable != null) { - handleConnectionException(info, throwable); + handleConnectionException(server, throwable); return; } @@ -387,7 +398,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { // Ignored; the plugin probably already handled this. break; case SERVER_DISCONNECTED: - handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); + handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); break; } }, connection.getChannel().eventLoop()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index 54899d2a1..6ed3c805c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -5,12 +5,12 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java index 64273473a..c25298bef 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.connection.client; -import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; public class InitialConnectSessionHandler implements MinecraftSessionHandler { private final ConnectedPlayer player; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index ff728178f..116351ebb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -2,22 +2,23 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.proxy.InboundConnection; -import com.velocitypowered.api.proxy.server.ServerInfo; -import com.velocitypowered.proxy.config.PlayerInfoForwarding; -import com.velocitypowered.proxy.connection.VelocityConstants; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.*; -import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.EncryptionUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -39,6 +40,7 @@ import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; public class LoginSessionHandler implements MinecraftSessionHandler { + private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final String MOJANG_SERVER_AUTH_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; @@ -157,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { return; } - if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) { + if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) { // Request encryption. EncryptionRequest request = generateRequest(); this.verify = Arrays.copyOf(request.getVerifyToken(), 4); @@ -218,7 +220,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } private void handleProxyLogin(ConnectedPlayer player) { - Optional toTry = player.getNextServerToTry(); + Optional toTry = player.getNextServerToTry(); if (!toTry.isPresent()) { player.close(TextComponent.of("No available servers", TextColor.RED)); return; @@ -244,7 +246,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { logger.info("{} has connected", player); inbound.setSessionHandler(new InitialConnectSessionHandler(player)); - player.createConnectionRequest(toTry.get()).fireAndForget(); + server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index ccc53a8ac..7c4e84376 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -4,16 +4,16 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.VelocityConfiguration; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.packet.StatusPing; import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusResponse; -import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.api.proxy.server.ServerPing; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java index b2ff70a41..727750d62 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java @@ -7,6 +7,7 @@ public class ConnectionMessages { public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED); public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED); public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error"); + public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?"); private ConnectionMessages() { throw new AssertionError(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index fcb1d34f9..4e233ddce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -4,7 +4,9 @@ import com.velocitypowered.proxy.VelocityServer; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import net.minecrell.terminalconsole.SimpleTerminalConsole; -import org.jline.reader.*; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; import java.util.List; import java.util.Optional; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java b/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java index 1fb47cce1..c6feff212 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java @@ -2,10 +2,10 @@ package com.velocitypowered.proxy.messages; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.proxy.messages.*; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.ChannelRegistrar; +import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import java.util.Collection; import java.util.Map; @@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public class VelocityChannelRegistrar implements ChannelRegistrar { - private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class); - private final Map handlers = new ConcurrentHashMap<>(); private final Map identifierMap = new ConcurrentHashMap<>(); @Override - public void register(MessageHandler handler, ChannelIdentifier... identifiers) { + public void register(ChannelIdentifier... identifiers) { for (ChannelIdentifier identifier : identifiers) { Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier, "identifier is unknown"); } for (ChannelIdentifier identifier : identifiers) { - handlers.put(identifier.getId(), handler); identifierMap.put(identifier.getId(), identifier); } } - public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) { - MessageHandler handler = handlers.get(message.getChannel()); - ChannelIdentifier identifier = identifierMap.get(message.getChannel()); - if (handler == null || identifier == null) { - return MessageHandler.ForwardStatus.FORWARD; - } - - try { - return handler.handle(source, side, identifier, message.getData()); - } catch (Exception e) { - logger.info("Unable to handle plugin message on channel {} for {}", message.getChannel(), source); - // In case of doubt, do not forward the message on. - return MessageHandler.ForwardStatus.HANDLED; - } - } - @Override public void unregister(ChannelIdentifier... identifiers) { for (ChannelIdentifier identifier : identifiers) { @@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar { } for (ChannelIdentifier identifier : identifiers) { - handlers.remove(identifier.getId()); identifierMap.remove(identifier.getId()); } } @@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar { public boolean registered(String id) { return identifierMap.containsKey(id); } + + public ChannelIdentifier getFromId(String id) { + return identifierMap.get(id); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index cc37d62b2..65301cd77 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -7,21 +7,11 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler; -import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder; -import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder; -import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; -import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; -import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; -import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; +import com.velocitypowered.proxy.protocol.netty.*; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; -import io.netty.channel.epoll.Epoll; -import io.netty.channel.epoll.EpollDatagramChannel; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.epoll.*; import io.netty.channel.kqueue.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramChannel; @@ -75,7 +65,7 @@ public final class ConnectionManager { @Override protected void initChannel(final Channel ch) { ch.pipeline() - .addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) + .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS)) .addLast(LEGACY_PING_DECODER, new LegacyPingDecoder()) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE) @@ -125,7 +115,9 @@ public final class ConnectionManager { public Bootstrap createWorker() { return new Bootstrap() .channel(this.transportType.socketChannelClass) - .group(this.workerGroup); + .group(this.workerGroup) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout()); } public void shutdown() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java index 518a3acbe..fb248dc6a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java @@ -13,7 +13,4 @@ public interface Connections { String MINECRAFT_DECODER = "minecraft-decoder"; String MINECRAFT_ENCODER = "minecraft-encoder"; String READ_TIMEOUT = "read-timeout"; - - int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy - int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java b/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java index 24ab295da..f14f10c40 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java @@ -2,7 +2,7 @@ package com.velocitypowered.proxy.network.http; import com.velocitypowered.proxy.VelocityServer; import io.netty.bootstrap.Bootstrap; -import io.netty.channel.*; +import io.netty.channel.Channel; import io.netty.channel.pool.*; import io.netty.handler.codec.http.*; import io.netty.handler.ssl.SslContext; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java index 3f84e6279..b93afb3fd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -13,7 +13,10 @@ import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory; import net.kyori.event.EventSubscriber; import net.kyori.event.PostResult; import net.kyori.event.SimpleEventBus; -import net.kyori.event.method.*; +import net.kyori.event.method.EventExecutor; +import net.kyori.event.method.MethodScanner; +import net.kyori.event.method.MethodSubscriptionAdapter; +import net.kyori.event.method.SimpleMethodSubscriptionAdapter; import net.kyori.event.method.asm.ASMEventExecutorFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.reflect.Method; import java.net.URL; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; public class VelocityEventManager implements EventManager { private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java index f4625a900..3c1d5b2c5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.plugin; -import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.proxy.VelocityServer; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java index dc26dec3e..09d1df949 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java @@ -2,7 +2,9 @@ package com.velocitypowered.proxy.plugin.loader; import com.google.inject.Guice; import com.google.inject.Injector; -import com.velocitypowered.api.plugin.*; +import com.velocitypowered.api.plugin.InvalidPluginException; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.proxy.VelocityServer; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java index cc3ddadc2..66e116eeb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java @@ -8,7 +8,6 @@ import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.proxy.VelocityServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 91fab4eb4..8056c3823 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -8,7 +8,7 @@ import io.netty.util.collection.IntObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.*; +import java.util.Objects; import java.util.function.Supplier; import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; @@ -23,7 +23,7 @@ public enum StateRegistry { }, STATUS { { - SERVERBOUND.register(StatusRequest.class, StatusRequest::new, + SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE, genericMappings(0x00)); SERVERBOUND.register(StatusPing.class, StatusPing::new, genericMappings(0x01)); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java index df093d78d..2628bc445 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -101,9 +101,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler queryResponse.writeByte(QUERY_TYPE_STAT); queryResponse.writeInt(sessionId); - // Fetch information - Collection players = server.getAllPlayers(); - // Start writing the response ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0); responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent())); @@ -114,12 +111,14 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler responseWriter.write("plugins", ""); responseWriter.write("map", "Velocity"); - responseWriter.write("numplayers", players.size()); + responseWriter.write("numplayers", server.getPlayerCount()); responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers()); responseWriter.write("hostport", server.getConfiguration().getBind().getPort()); responseWriter.write("hostip", server.getConfiguration().getBind().getHostString()); - responseWriter.writePlayers(players); + if (!responseWriter.isBasic) { + responseWriter.writePlayers(server.getAllPlayers()); + } break; } @@ -132,6 +131,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler ctx.writeAndFlush(responsePacket); } catch (Exception e) { logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e); + // NB: Only need to explicitly release upon exception, writing the response out will decrement the reference + // count. responsePacket.release(); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java index 61893ff44..95b218056 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java @@ -1,8 +1,8 @@ package com.velocitypowered.proxy.protocol.netty; import com.google.common.base.Preconditions; -import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.natives.compression.VelocityCompressor; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java index 802f45121..48ded65a5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.protocol.netty; -import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.natives.compression.VelocityCompressor; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index 07ddb287f..4b7e00232 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -1,7 +1,10 @@ package com.velocitypowered.proxy.protocol.netty; import com.google.common.base.Preconditions; -import com.velocitypowered.proxy.protocol.*; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.StateRegistry; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.CorruptedFrameException; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java index 7a0d61c92..3116c6a69 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java @@ -4,7 +4,6 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToMessageEncoder; import java.util.List; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java index c3de9c6b9..f289b7c54 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java @@ -1,8 +1,8 @@ package com.velocitypowered.proxy.protocol.packet; import com.google.common.base.Preconditions; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import net.kyori.text.Component; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java index 1420b7981..f6dfd877a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java @@ -1,8 +1,8 @@ package com.velocitypowered.proxy.protocol.packet; import com.google.common.base.Preconditions; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import net.kyori.text.Component; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java index dec9ecb4a..aef59ec2b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java index 35c75af30..6ee851d65 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java @@ -2,13 +2,13 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction; -import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString; - import io.netty.buffer.ByteBuf; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializer; import net.kyori.text.serializer.ComponentSerializers; +import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString; + public class HeaderAndFooter implements MinecraftPacket { private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java index 638b15d52..1e491b61b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java index 459afe338..a9cb0ac68 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java index c53276e5a..33e1e1411 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java @@ -1,10 +1,16 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import io.netty.buffer.ByteBuf; public class StatusRequest implements MinecraftPacket { + public static final StatusRequest INSTANCE = new StatusRequest(); + + private StatusRequest() { + + } + @Override public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { @@ -14,4 +20,9 @@ public class StatusRequest implements MinecraftPacket { public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { } + + @Override + public String toString() { + return "StatusRequest"; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java index 75a052258..7c397fa83 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java index 075058486..1ffb37dc9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import java.nio.charset.StandardCharsets; @@ -20,13 +19,20 @@ public enum PluginMessageUtil { return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand"); } + public static boolean isMCRegister(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register"); + } + + public static boolean isMCUnregister(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister"); + } + public static List getChannels(PluginMessage message) { Preconditions.checkNotNull(message, "message"); - Preconditions.checkArgument(message.getChannel().equals("REGISTER") || - message.getChannel().equals("UNREGISTER") || - message.getChannel().equals("minecraft:register") || - message.getChannel().equals("minecraft:unregister"), - "Unknown channel type " + message.getChannel()); + Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s", + message.getChannel()); String channels = new String(message.getData(), StandardCharsets.UTF_8); return ImmutableList.copyOf(channels.split("\0")); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java index 6e10807e3..d37abc0e3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -16,7 +16,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; public class VelocityScheduler implements Scheduler { private final PluginManager pluginManager; @@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler { } @Override - public TaskBuilder delay(int time, TimeUnit unit) { + public TaskBuilder delay(long time, TimeUnit unit) { this.delay = unit.toMillis(time); return this; } @Override - public TaskBuilder repeat(int time, TimeUnit unit) { + public TaskBuilder repeat(long time, TimeUnit unit) { this.repeat = unit.toMillis(time); return this; } @@ -91,62 +90,9 @@ public class VelocityScheduler implements Scheduler { @Override public ScheduledTask schedule() { - if (delay == 0 && repeat == 0) { - // A special purpose, simplified implementation - VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable); - tasksByPlugin.put(plugin, task); - taskService.execute(task); - return task; - } else { - VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat); - tasksByPlugin.put(plugin, task); - return task; - } - } - } - - private class VelocityImmediatelyScheduledTask implements ScheduledTask, Runnable { - private final Object plugin; - private final Runnable runnable; - private final AtomicReference status; - private Thread taskThread; - - private VelocityImmediatelyScheduledTask(Object plugin, Runnable runnable) { - this.plugin = plugin; - this.runnable = runnable; - this.status = new AtomicReference<>(TaskStatus.SCHEDULED); - } - - @Override - public Object plugin() { - return plugin; - } - - @Override - public TaskStatus status() { - return status.get(); - } - - @Override - public void cancel() { - if (status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.CANCELLED)) { - if (taskThread != null) { - taskThread.interrupt(); - } - } - } - - @Override - public void run() { - taskThread = Thread.currentThread(); - try { - runnable.run(); - } catch (Exception e) { - Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); - } - status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.FINISHED); - taskThread = null; - tasksByPlugin.remove(plugin, this); + VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat); + tasksByPlugin.put(plugin, task); + return task; } } @@ -154,6 +100,7 @@ public class VelocityScheduler implements Scheduler { private final Object plugin; private final Runnable runnable; private ScheduledFuture future; + private volatile Thread currentTaskThread; private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) { this.plugin = plugin; @@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler { public void cancel() { if (future != null) { future.cancel(false); + + Thread cur = currentTaskThread; + if (cur != null) { + cur.interrupt(); + } + onFinish(); } } @@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler { @Override public void run() { taskService.execute(() -> { + currentTaskThread = Thread.currentThread(); try { runnable.run(); } catch (Exception e) { @@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler { Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); } } + currentTaskThread = null; }); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java new file mode 100644 index 000000000..2216387e7 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java @@ -0,0 +1,49 @@ +package com.velocitypowered.proxy.server; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.VelocityServer; + +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class ServerMap { + private final VelocityServer server; + private final Map servers = new ConcurrentHashMap<>(); + + public ServerMap(VelocityServer server) { + this.server = server; + } + + public Optional getServer(String name) { + Preconditions.checkNotNull(name, "server"); + String lowerName = name.toLowerCase(Locale.US); + return Optional.ofNullable(servers.get(lowerName)); + } + + public Collection getAllServers() { + return ImmutableList.copyOf(servers.values()); + } + + public RegisteredServer register(ServerInfo serverInfo) { + Preconditions.checkNotNull(serverInfo, "serverInfo"); + String lowerName = serverInfo.getName().toLowerCase(Locale.US); + VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo); + Preconditions.checkArgument(servers.putIfAbsent(lowerName, rs) == null, "Server with name %s already registered", serverInfo.getName()); + return rs; + } + + public void unregister(ServerInfo serverInfo) { + Preconditions.checkNotNull(serverInfo, "serverInfo"); + String lowerName = serverInfo.getName().toLowerCase(Locale.US); + RegisteredServer rs = servers.get(lowerName); + Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName()); + Preconditions.checkArgument(rs.getServerInfo().equals(serverInfo), "Trying to remove server %s with differing information", serverInfo.getName()); + Preconditions.checkState(servers.remove(lowerName, rs), "Server with name %s replaced whilst unregistering", serverInfo.getName()); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java new file mode 100644 index 000000000..7abd379f0 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -0,0 +1,112 @@ +package com.velocitypowered.proxy.server; + +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; +import com.velocitypowered.proxy.server.ping.PingSessionHandler; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelInitializer; +import io.netty.handler.timeout.ReadTimeoutHandler; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static com.velocitypowered.proxy.network.Connections.*; + +public class VelocityRegisteredServer implements RegisteredServer { + private final VelocityServer server; + private final ServerInfo serverInfo; + private final Set players = ConcurrentHashMap.newKeySet(); + + public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) { + this.server = server; + this.serverInfo = serverInfo; + } + + @Override + public ServerInfo getServerInfo() { + return serverInfo; + } + + @Override + public Collection getPlayersConnected() { + return ImmutableList.copyOf(players); + } + + @Override + public CompletableFuture ping() { + CompletableFuture pingFuture = new CompletableFuture<>(); + server.initializeGenericBootstrap() + .handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline() + .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS)) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) + .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) + .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND)); + + MinecraftConnection connection = new MinecraftConnection(ch, server); + connection.setState(StateRegistry.HANDSHAKE); + ch.pipeline().addLast(HANDLER, connection); + } + }) + .connect(serverInfo.getAddress()) + .addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); + conn.setSessionHandler(new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn)); + } else { + pingFuture.completeExceptionally(future.cause()); + } + } + }); + return pingFuture; + } + + public void addPlayer(ConnectedPlayer player) { + players.add(player); + } + + public void removePlayer(ConnectedPlayer player) { + players.remove(player); + } + + @Override + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + for (ConnectedPlayer player : players) { + if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) { + ServerConnection connection = player.getConnectedServer(); + return connection.sendPluginMessage(identifier, data); + } + } + + return false; + } + + @Override + public String toString() { + return "registered server: " + serverInfo; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java new file mode 100644 index 000000000..0bce28b74 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java @@ -0,0 +1,68 @@ +package com.velocitypowered.proxy.server.ping; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.protocol.packet.Handshake; +import com.velocitypowered.proxy.protocol.packet.StatusRequest; +import com.velocitypowered.proxy.protocol.packet.StatusResponse; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +public class PingSessionHandler implements MinecraftSessionHandler { + private final CompletableFuture result; + private final RegisteredServer server; + private final MinecraftConnection connection; + private boolean completed = false; + + public PingSessionHandler(CompletableFuture result, RegisteredServer server, MinecraftConnection connection) { + this.result = result; + this.server = server; + this.connection = connection; + } + + @Override + public void activated() { + Handshake handshake = new Handshake(); + handshake.setNextStatus(StateRegistry.STATUS_ID); + handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); + handshake.setPort(server.getServerInfo().getAddress().getPort()); + handshake.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION); + connection.write(handshake); + + connection.setState(StateRegistry.STATUS); + connection.write(StatusRequest.INSTANCE); + } + + @Override + public void handle(MinecraftPacket packet) { + Preconditions.checkState(packet instanceof StatusResponse, "Did not get status response back from connection"); + + // All good! + completed = true; + connection.close(); + + ServerPing ping = VelocityServer.GSON.fromJson(((StatusResponse) packet).getStatus(), ServerPing.class); + result.complete(ping); + } + + @Override + public void disconnected() { + if (!completed) { + result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server")); + } + } + + @Override + public void exception(Throwable throwable) { + completed = true; + result.completeExceptionally(throwable); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java deleted file mode 100644 index 75784ef0b..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.velocitypowered.proxy.util; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.proxy.server.ServerInfo; - -import java.util.*; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class ServerMap { - private final Map servers = new HashMap<>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - public Optional getServer(String server) { - Preconditions.checkNotNull(server, "server"); - String lowerName = server.toLowerCase(Locale.US); - lock.readLock().lock(); - try { - return Optional.ofNullable(servers.get(lowerName)); - } finally { - lock.readLock().unlock(); - } - } - - public Collection getAllServers() { - lock.readLock().lock(); - try { - return ImmutableList.copyOf(servers.values()); - } finally { - lock.readLock().unlock(); - } - } - - public void register(ServerInfo server) { - Preconditions.checkNotNull(server, "server"); - String lowerName = server.getName().toLowerCase(Locale.US); - lock.writeLock().lock(); - try { - Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName()); - } finally { - lock.writeLock().unlock(); - } - } - - public void unregister(ServerInfo server) { - Preconditions.checkNotNull(server, "server"); - String lowerName = server.getName().toLowerCase(Locale.US); - lock.writeLock().lock(); - try { - Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!"); - } finally { - lock.writeLock().unlock(); - } - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java b/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java index ae289bb1c..1de4b165d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java @@ -6,7 +6,6 @@ import com.google.common.collect.MapMaker; import java.util.Collections; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; /** diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java index dac88e563..cc227f471 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -3,9 +3,7 @@ package com.velocitypowered.proxy.protocol; import com.velocitypowered.proxy.protocol.packet.Handshake; import org.junit.jupiter.api.Test; -import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12; -import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1; -import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; import static org.junit.jupiter.api.Assertions.*; class PacketRegistryTest { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java index d6d6896ee..7be0008c8 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java @@ -9,7 +9,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class VelocitySchedulerTest { // TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep() diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java index b9f9ff204..ba97fc493 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java @@ -7,7 +7,8 @@ import java.net.InetAddress; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class RatelimiterTest { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java index e332ed85f..4105fdac7 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java @@ -1,31 +1,34 @@ package com.velocitypowered.proxy.util; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.server.ServerMap; import org.junit.jupiter.api.Test; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class ServerMapTest { private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565); @Test void respectsCaseInsensitivity() { - ServerMap map = new ServerMap(); + ServerMap map = new ServerMap(null); ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); - map.register(info); + RegisteredServer connection = map.register(info); - assertEquals(Optional.of(info), map.getServer("TestServer")); - assertEquals(Optional.of(info), map.getServer("testserver")); - assertEquals(Optional.of(info), map.getServer("TESTSERVER")); + assertEquals(Optional.of(connection), map.getServer("TestServer")); + assertEquals(Optional.of(connection), map.getServer("testserver")); + assertEquals(Optional.of(connection), map.getServer("TESTSERVER")); } @Test void rejectsRepeatedRegisterAttempts() { - ServerMap map = new ServerMap(); + ServerMap map = new ServerMap(null); ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); map.register(info);