diff --git a/proxy/build.gradle b/proxy/build.gradle index 1901a221b..140e50405 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -38,6 +38,7 @@ dependencies { compile "org.apache.logging.log4j:log4j-core:${log4jVersion}" compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}" + compile 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options compile 'net.minecrell:terminalconsoleappender:1.1.1' runtime 'net.java.dev.jna:jna:4.5.2' // Needed for JLine runtime 'com.lmax:disruptor:3.4.2' // Async loggers diff --git a/proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java b/proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java new file mode 100644 index 000000000..30a617b9f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java @@ -0,0 +1,46 @@ +package com.velocitypowered.proxy; + +import java.io.IOException; +import java.util.Arrays; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class ProxyOptions { + private static final Logger logger = LogManager.getLogger(ProxyOptions.class); + private final boolean help; + private final @Nullable Integer port; + + ProxyOptions(final String[] args) { + final OptionParser parser = new OptionParser(); + + final OptionSpec help = parser.acceptsAll(Arrays.asList("h", "help"), "Print help") + .forHelp(); + final OptionSpec port = parser.acceptsAll(Arrays.asList("p", "port"), + "Specify the bind port to be used. The configuration bind port will be ignored.") + .withRequiredArg().ofType(Integer.class); + final OptionSet set = parser.parse(args); + + this.help = set.has(help); + this.port = port.value(set); + + if (this.help) { + try { + parser.printHelpOn(System.out); + } catch (final IOException e) { + logger.error("Could not print help", e); + } + } + } + + boolean isHelp() { + return this.help; + } + + public @Nullable Integer getPort() { + return this.port; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index 0d3b64c15..8774c0b96 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -17,7 +17,13 @@ public class Velocity { public static void main(String... args) { long startTime = System.currentTimeMillis(); - VelocityServer server = new VelocityServer(); + final ProxyOptions options = new ProxyOptions(args); + + if (options.isHelp()) { + return; + } + + VelocityServer server = new VelocityServer(options); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> server.shutdown(false), "Shutdown thread")); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index b9d061ba5..c803e2ff1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -75,6 +75,8 @@ public class VelocityServer implements ProxyServer { .create(); private ConnectionManager cm; + private final ProxyOptions options; + private @MonotonicNonNull ConnectionManager cm; private @MonotonicNonNull VelocityConfiguration configuration; private @MonotonicNonNull NettyHttpClient httpClient; private @MonotonicNonNull KeyPair serverKeyPair; @@ -92,13 +94,14 @@ public class VelocityServer implements ProxyServer { private VelocityScheduler scheduler; private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar(); - VelocityServer() { + VelocityServer(final ProxyOptions options) { pluginManager = new VelocityPluginManager(this); eventManager = new VelocityEventManager(pluginManager); scheduler = new VelocityScheduler(pluginManager); console = new VelocityConsole(this); cm = new ConnectionManager(this); servers = new ServerMap(this); + this.options = options; } public KeyPair getServerKeyPair() { @@ -188,7 +191,13 @@ public class VelocityServer implements ProxyServer { // init console permissions after plugins are loaded console.setupPermissions(); - this.cm.bind(configuration.getBind()); + final Integer port = this.options.getPort(); + if (port != null) { + logger.debug("Overriding bind port to {} from command line option", port); + this.cm.bind(new InetSocketAddress(configuration.getBind().getHostString(), port)); + } else { + this.cm.bind(configuration.getBind()); + } if (configuration.isQueryEnabled()) { this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java index e3e8d5d6d..ba94083c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java @@ -17,11 +17,6 @@ public class ShutdownCommand implements Command { @Override public void execute(CommandSource source, String @NonNull [] args) { - if (source != server.getConsoleCommandSource()) { - source - .sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED)); - return; - } server.shutdown(true); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java index c6c9a99aa..57baa0106 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -97,7 +97,7 @@ public class VelocityCommand implements Command { @Override public boolean hasPermission(CommandSource source, String @NonNull [] args) { if (args.length == 0) { - return true; + return subcommands.values().stream().anyMatch(e -> e.hasPermission(source, args)); } Command command = subcommands.get(args[0].toLowerCase(Locale.US)); if (command == null) { @@ -151,6 +151,11 @@ public class VelocityCommand implements Command { @Override public void execute(CommandSource source, String @NonNull [] args) { + if (args.length != 0) { + source.sendMessage(TextComponent.of("/velocity version", TextColor.RED)); + return; + } + ProxyVersion version = server.getVersion(); TextComponent velocity = TextComponent.builder(version.getName() + " ") 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 20fe7097a..a7e2e69af 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -23,7 +23,7 @@ public class VelocityCommandManager implements CommandManager { Preconditions.checkNotNull(command, "executor"); for (int i = 0, length = aliases.length; i < length; i++) { final String alias = aliases[i]; - Preconditions.checkNotNull(aliases, "alias at index %s", i); + Preconditions.checkNotNull(alias, "alias at index %s", i); this.commands.put(alias.toLowerCase(Locale.ENGLISH), command); } } 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 3be1bfeba..04f03f764 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 @@ -160,8 +160,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { public void disconnected() { serverConn.getServer().removePlayer(serverConn.getPlayer()); if (!serverConn.isGracefulDisconnect()) { - serverConn.getPlayer().handleConnectionException(serverConn.getServer(), Disconnect.create( - ConnectionMessages.UNEXPECTED_DISCONNECT)); + serverConn.getPlayer().disconnect(ConnectionMessages.UNEXPECTED_DISCONNECT); } } 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 aff20854c..47e18c9b1 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 @@ -94,6 +94,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, new LoginSessionHandler(server, VelocityServerConnection.this, result)); startHandshake(); } else { + // We need to remember to reset the in-flight connection to allow connect() to work + // properly. + proxyPlayer.resetInFlightConnection(); result.completeExceptionally(future.cause()); } }); 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 2c568da64..ccf6dbc44 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 @@ -286,6 +286,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return connectedServer; } + public void resetInFlightConnection() { + connectionInFlight = null; + } + public void handleConnectionException(RegisteredServer server, Throwable throwable) { if (throwable == null) { throw new NullPointerException("throwable"); @@ -481,7 +485,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private Optional checkServer(RegisteredServer server) { Preconditions .checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server."); - if (connectionInFlight != null) { + if (connectionInFlight != null || (connectedServer != null + && !connectedServer.hasCompletedJoin())) { return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS); } if (connectedServer != null && connectedServer.getServer().equals(server)) { @@ -516,8 +521,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return CompletableFuture .completedFuture(ConnectionRequestResults.plainResult(lastCheck.get())); } - return new VelocityServerConnection((VelocityRegisteredServer) rs, - ConnectedPlayer.this, server).connect(); + + VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs; + VelocityServerConnection con = new VelocityServerConnection(vrs, ConnectedPlayer.this, + server); + connectionInFlight = con; + return con.connect(); }); } 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 f329b7f31..2695298a1 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 @@ -264,6 +264,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler return; } + writeString(buf, "plugins"); + StringBuilder pluginsString = new StringBuilder(); pluginsString.append(serverVersion).append(':').append(' '); Iterator iterator = plugins.iterator();