From 22d1398f73f51c11f354a4f37e31b1a7d031123f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 5 Aug 2018 01:26:07 -0400 Subject: [PATCH 01/88] Extend the API with a ProxyServer type. --- .../api/proxy/ProxyServer.java | 32 ++++++++++++++ .../velocitypowered/proxy/VelocityServer.java | 43 ++++++++++++++++++- .../connection/client/ConnectedPlayer.java | 1 + .../client/LoginSessionHandler.java | 1 + .../netty/MinecraftCompressEncoder.java | 9 ++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java new file mode 100644 index 000000000..7ff2d8ea9 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -0,0 +1,32 @@ +package com.velocitypowered.api.proxy; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; + +/** + * Represents a Minecraft proxy server that follows the Velocity API. + */ +public interface ProxyServer { + /** + * Retrieves the player currently connected to this proxy by their Minecraft username. + * @param username the username + * @return an {@link Optional} with the player + */ + Optional getPlayer(@Nonnull String username); + + /** + * Retrieves the player currently connected to this proxy by their Minecraft UUID. + * @param uuid the UUID + * @return an {@link Optional} with the player + */ + Optional getPlayer(@Nonnull UUID uuid); + + /** + * Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players + * online. + * @return the players online on this proxy + */ + Collection getAllPlayers(); +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index cedf2910e..174ff6012 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -1,10 +1,16 @@ package com.velocitypowered.proxy; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.network.ConnectionManager; import com.velocitypowered.proxy.config.VelocityConfiguration; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.util.AddressUtil; @@ -16,15 +22,20 @@ import net.kyori.text.serializer.GsonComponentSerializer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.annotation.Nonnull; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyPair; +import java.util.Collection; import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; -public class VelocityServer { +public class VelocityServer implements ProxyServer { private static final Logger logger = LogManager.getLogger(VelocityServer.class); private static final VelocityServer INSTANCE = new VelocityServer(); public static final Gson GSON = new GsonBuilder() @@ -37,6 +48,9 @@ public class VelocityServer { private KeyPair serverKeyPair; private final ServerMap servers = new ServerMap(); + private final Map connectionsByUuid = new ConcurrentHashMap<>(); + private final Map connectionsByName = new ConcurrentHashMap<>(); + private VelocityServer() { } @@ -103,4 +117,31 @@ public class VelocityServer { public NettyHttpClient getHttpClient() { return httpClient; } + + public void registerConnection(ConnectedPlayer connection) { + connectionsByName.put(connection.getUsername(), connection); + connectionsByUuid.put(connection.getUniqueId(), connection); + } + + public void unregisterConnection(ConnectedPlayer connection) { + connectionsByName.remove(connection.getUsername(), connection); + connectionsByUuid.remove(connection.getUniqueId(), connection); + } + + @Override + public Optional getPlayer(@Nonnull String username) { + Preconditions.checkNotNull(username, "username"); + return Optional.ofNullable(connectionsByName.get(username)); + } + + @Override + public Optional getPlayer(@Nonnull UUID uuid) { + Preconditions.checkNotNull(uuid, "uuid"); + return Optional.ofNullable(connectionsByUuid.get(uuid)); + } + + @Override + public Collection getAllPlayers() { + return ImmutableList.copyOf(connectionsByUuid.values()); + } } 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 55f89951a..dda03be7e 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 @@ -209,6 +209,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { if (connectedServer != null) { connectedServer.disconnect(); } + VelocityServer.getServer().unregisterConnection(this); } @Override 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 12e69a4eb..1826baa48 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 @@ -140,6 +140,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { inbound.setAssociation(player); inbound.setState(StateRegistry.PLAY); inbound.setSessionHandler(new InitialConnectSessionHandler(player)); + VelocityServer.getServer().registerConnection(player); player.createConnectionRequest(toTry.get()).fireAndForget(); } } 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 f62834b93..802f45121 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 @@ -28,6 +28,15 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder { } } + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { + if (msg.readableBytes() <= threshold) { + return ctx.alloc().directBuffer(msg.readableBytes() + 1); + } + // A reasonable assumption about compression savings + return ctx.alloc().directBuffer(msg.readableBytes() / 3); + } + @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { compressor.dispose(); From 4e64b04464d6f573f919c096457a0f67bf690edd Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 5 Aug 2018 01:30:40 -0400 Subject: [PATCH 02/88] Avoid duplicate logins. --- .../com/velocitypowered/proxy/VelocityServer.java | 12 +++++++++--- .../proxy/connection/client/LoginSessionHandler.java | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 174ff6012..a18cd2db8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -118,9 +118,15 @@ public class VelocityServer implements ProxyServer { return httpClient; } - public void registerConnection(ConnectedPlayer connection) { - connectionsByName.put(connection.getUsername(), connection); - connectionsByUuid.put(connection.getUniqueId(), connection); + public boolean registerConnection(ConnectedPlayer connection) { + if (connectionsByName.putIfAbsent(connection.getUsername(), connection) != null) { + return false; + } + if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) { + connectionsByName.remove(connection.getUsername(), connection); + return false; + } + return true; } public void unregisterConnection(ConnectedPlayer connection) { 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 1826baa48..d97bdb11f 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 @@ -136,11 +136,15 @@ public class LoginSessionHandler implements MinecraftSessionHandler { success.setUuid(profile.idAsUuid()); inbound.write(success); - logger.info("{} has connected", player); inbound.setAssociation(player); inbound.setState(StateRegistry.PLAY); + + if (!VelocityServer.getServer().registerConnection(player)) { + inbound.closeWith(Disconnect.create(TextComponent.of("You are already on this proxy!", TextColor.RED))); + } + + logger.info("{} has connected", player); inbound.setSessionHandler(new InitialConnectSessionHandler(player)); - VelocityServer.getServer().registerConnection(player); player.createConnectionRequest(toTry.get()).fireAndForget(); } } From 23a6488a39d01dcb3946124e91147f3728d37ee3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 5 Aug 2018 16:12:16 -0400 Subject: [PATCH 03/88] Add default config again. Closes #12 --- .gitignore | 2 +- proxy/src/main/resources/velocity.toml | 41 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 proxy/src/main/resources/velocity.toml diff --git a/.gitignore b/.gitignore index 00b45a827..7ae951cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -121,4 +121,4 @@ gradle-app.setting # Other trash logs/ -velocity.toml \ No newline at end of file +/velocity.toml \ No newline at end of file diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml new file mode 100644 index 000000000..cb4c6fbb5 --- /dev/null +++ b/proxy/src/main/resources/velocity.toml @@ -0,0 +1,41 @@ +# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577. +bind = "0.0.0.0:25577" + +# What should be the MOTD? Legacy color codes and JSON are accepted. +motd = "&3A Velocity Server" + +# What should we display for the maximum number of players? (Velocity does not support a cap +# on the number of players online.) +show-max-players = 500 + +# Should we authenticate players with Mojang? By default, this is on. +online-mode = true + +# Should we forward IP addresses and other data to backend servers? +# Available options: +# - "none": No forwarding will be done. All players will appear to be connecting from the proxy +# and will have offline-mode UUIDs. +# - "legacy": Forward player IPs and UUIDs in BungeeCord-compatible fashion. Use this if you run +# servers using Minecraft 1.12 or lower. +# - "modern": Forward player IPs and UUIDs as part of the login process using Velocity's native +# forwarding. Only applicable for Minecraft 1.13 or higher. +ip-forwarding = "modern" + +[servers] +# Configure your servers here. +lobby = "127.0.0.1:30066" +factions = "127.0.0.1:30067" +minigames = "127.0.0.1:30068" + +# In what order we should try servers when a player logs in or is kicked from a server. +try = [ + "lobby" +] + +[advanced] +# How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and +# setting it to -1 will disable compression entirely. +compression-threshold = 1024 + +# How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6. +compression-level = -1 \ No newline at end of file From a778825152973f15a12abece8434e41b62381a18 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 5 Aug 2018 17:10:18 -0400 Subject: [PATCH 04/88] Add server registration API. --- .../api/proxy/ProxyServer.java | 27 ++++++++++++ .../velocitypowered/proxy/VelocityServer.java | 21 ++++++++++ .../velocitypowered/proxy/util/ServerMap.java | 42 +++++++++++++++---- 3 files changed, 83 insertions(+), 7 deletions(-) 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 7ff2d8ea9..7e23a0a07 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -1,5 +1,7 @@ package com.velocitypowered.api.proxy; +import com.velocitypowered.api.server.ServerInfo; + import javax.annotation.Nonnull; import java.util.Collection; import java.util.Optional; @@ -29,4 +31,29 @@ public interface ProxyServer { * @return the players online on this proxy */ Collection getAllPlayers(); + + /** + * Retrieves a registered {@link ServerInfo} instance by its name. + * @param name the name of the server + * @return the server + */ + Optional getServerInfo(@Nonnull String name); + + /** + * Retrieves all {@link ServerInfo}s registered with this proxy. + * @return the servers registered with this proxy + */ + Collection getAllServers(); + + /** + * Registers a server with this proxy. A server with this name should not already exist. + * @param server the server to register + */ + void registerServer(@Nonnull ServerInfo server); + + /** + * Unregisters this server from the proxy. + * @param server the server to unregister + */ + void unregisterServer(@Nonnull ServerInfo server); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index a18cd2db8..71659208b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -150,4 +150,25 @@ public class VelocityServer implements ProxyServer { public Collection getAllPlayers() { return ImmutableList.copyOf(connectionsByUuid.values()); } + + @Override + public Optional getServerInfo(@Nonnull String name) { + Preconditions.checkNotNull(name, "name"); + return servers.getServer(name); + } + + @Override + public Collection getAllServers() { + return servers.getAllServers(); + } + + @Override + public void registerServer(@Nonnull ServerInfo server) { + servers.register(server); + } + + @Override + public void unregisterServer(@Nonnull ServerInfo server) { + servers.unregister(server); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java index eb0948c26..c31b77712 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java @@ -8,21 +8,49 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; +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 name) { - Preconditions.checkNotNull(name, "name"); - return Optional.ofNullable(servers.get(name.toLowerCase())); + public Optional getServer(String server) { + Preconditions.checkNotNull(server, "server"); + lock.readLock().lock(); + try { + return Optional.ofNullable(servers.get(server.toLowerCase())); + } finally { + lock.readLock().unlock(); + } } public Collection getAllServers() { - return ImmutableList.copyOf(servers.values()); + lock.readLock().lock(); + try { + return ImmutableList.copyOf(servers.values()); + } finally { + lock.readLock().unlock(); + } } - public void register(ServerInfo info) { - Preconditions.checkNotNull(info, "info"); - servers.put(info.getName(), info); + public void register(ServerInfo server) { + Preconditions.checkNotNull(server, "server"); + lock.writeLock().lock(); + try { + Preconditions.checkArgument(servers.putIfAbsent(server.getName(), server) == null, "Server with name %s already registered", server.getName()); + } finally { + lock.writeLock().unlock(); + } + } + + public void unregister(ServerInfo server) { + Preconditions.checkNotNull(server, "server"); + lock.writeLock().lock(); + try { + Preconditions.checkArgument(servers.remove(server.getName(), server), "Server with this name is not registered!"); + } finally { + lock.writeLock().unlock(); + } } } From 48822fe55cc73c06f82cc39b83498ff9462737b5 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 5 Aug 2018 17:14:39 -0400 Subject: [PATCH 05/88] Better case-insensitivity. --- .../com/velocitypowered/proxy/VelocityServer.java | 12 +++++------- .../com/velocitypowered/proxy/util/ServerMap.java | 14 +++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 71659208b..630ad4ef8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -29,10 +29,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyPair; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class VelocityServer implements ProxyServer { @@ -119,11 +116,12 @@ public class VelocityServer implements ProxyServer { } public boolean registerConnection(ConnectedPlayer connection) { - if (connectionsByName.putIfAbsent(connection.getUsername(), connection) != null) { + String lowerName = connection.getUsername().toLowerCase(Locale.US); + if (connectionsByName.putIfAbsent(lowerName, connection) != null) { return false; } if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) { - connectionsByName.remove(connection.getUsername(), connection); + connectionsByName.remove(lowerName, connection); return false; } return true; @@ -137,7 +135,7 @@ public class VelocityServer implements ProxyServer { @Override public Optional getPlayer(@Nonnull String username) { Preconditions.checkNotNull(username, "username"); - return Optional.ofNullable(connectionsByName.get(username)); + return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US))); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java index c31b77712..d961d719d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java @@ -4,10 +4,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.server.ServerInfo; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -17,9 +14,10 @@ public class ServerMap { public Optional getServer(String server) { Preconditions.checkNotNull(server, "server"); + String lowerName = server.toLowerCase(Locale.US); lock.readLock().lock(); try { - return Optional.ofNullable(servers.get(server.toLowerCase())); + return Optional.ofNullable(servers.get(lowerName)); } finally { lock.readLock().unlock(); } @@ -36,9 +34,10 @@ public class ServerMap { public void register(ServerInfo server) { Preconditions.checkNotNull(server, "server"); + String lowerName = server.getName().toLowerCase(Locale.US); lock.writeLock().lock(); try { - Preconditions.checkArgument(servers.putIfAbsent(server.getName(), server) == null, "Server with name %s already registered", server.getName()); + Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName()); } finally { lock.writeLock().unlock(); } @@ -46,9 +45,10 @@ public class ServerMap { public void unregister(ServerInfo server) { Preconditions.checkNotNull(server, "server"); + String lowerName = server.getName().toLowerCase(Locale.US); lock.writeLock().lock(); try { - Preconditions.checkArgument(servers.remove(server.getName(), server), "Server with this name is not registered!"); + Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!"); } finally { lock.writeLock().unlock(); } From b4f1d64cb0437a9a11f270318e3159c74a37ed0a Mon Sep 17 00:00:00 2001 From: Desetude Date: Mon, 6 Aug 2018 09:03:44 +0100 Subject: [PATCH 06/88] Add 1.8 support --- .../proxy/protocol/ProtocolConstants.java | 4 +++- .../proxy/protocol/StateRegistry.java | 15 +++++++++++++++ .../proxy/protocol/packet/ClientSettings.java | 10 ++++++++-- .../proxy/protocol/packet/JoinGame.java | 12 ++++++++++-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index 54b021775..421a04124 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -3,6 +3,7 @@ package com.velocitypowered.proxy.protocol; import java.util.Arrays; public enum ProtocolConstants { ; + public static final int MINECRAFT_1_8 = 47; public static final int MINECRAFT_1_9 = 107; public static final int MINECRAFT_1_9_1 = 108; public static final int MINECRAFT_1_9_2 = 109; @@ -15,9 +16,10 @@ public enum ProtocolConstants { ; public static final int MINECRAFT_1_12_2 = 340; public static final int MINECRAFT_1_13 = 393; - public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_9; + public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8; public static final int[] SUPPORTED_VERSIONS = new int[] { + MINECRAFT_1_8, MINECRAFT_1_9, MINECRAFT_1_9_1, MINECRAFT_1_9_2, 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 891cdf8c9..3658126df 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -32,21 +32,25 @@ public enum StateRegistry { PLAY { { SERVERBOUND.register(Chat.class, Chat::new, + map(0x01, MINECRAFT_1_8), map(0x02, MINECRAFT_1_9), map(0x03, MINECRAFT_1_12), map(0x02, MINECRAFT_1_12_2), map(0x02, MINECRAFT_1_13)); SERVERBOUND.register(ClientSettings.class, ClientSettings::new, + map(0x15, MINECRAFT_1_8), map(0x04, MINECRAFT_1_9), map(0x05, MINECRAFT_1_12), map(0x04, MINECRAFT_1_12_1), map(0x04, MINECRAFT_1_13)); SERVERBOUND.register(PluginMessage.class, PluginMessage::new, + map(0x17, MINECRAFT_1_8), map(0x09, MINECRAFT_1_9), map(0x0A, MINECRAFT_1_12), map(0x09, MINECRAFT_1_12_1), map(0x0A, MINECRAFT_1_13)); SERVERBOUND.register(KeepAlive.class, KeepAlive::new, + map(0x00, MINECRAFT_1_8), map(0x0B, MINECRAFT_1_9), map(0x0C, MINECRAFT_1_12), map(0x0B, MINECRAFT_1_12_1), @@ -56,46 +60,56 @@ public enum StateRegistry { map(0x0C, MINECRAFT_1_9), map(0x0C, MINECRAFT_1_12)); CLIENTBOUND.register(Chat.class, Chat::new, + map(0x02, MINECRAFT_1_8), map(0x0F, MINECRAFT_1_9), map(0x0F, MINECRAFT_1_12), map(0x0E, MINECRAFT_1_13)); CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, + map(0x3F, MINECRAFT_1_8), map(0x18, MINECRAFT_1_9), map(0x18, MINECRAFT_1_12), map(0x19, MINECRAFT_1_13)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, + map(0x40, MINECRAFT_1_8), map(0x1A, MINECRAFT_1_9), map(0x1A, MINECRAFT_1_12), map(0x1B, MINECRAFT_1_13)); CLIENTBOUND.register(KeepAlive.class, KeepAlive::new, + map(0x00, MINECRAFT_1_8), map(0x1F, MINECRAFT_1_9), map(0x1F, MINECRAFT_1_12), map(0x21, MINECRAFT_1_13)); CLIENTBOUND.register(JoinGame.class, JoinGame::new, + map(0x01, MINECRAFT_1_8), map(0x23, MINECRAFT_1_9), map(0x23, MINECRAFT_1_12), map(0x25, MINECRAFT_1_13)); CLIENTBOUND.register(Respawn.class, Respawn::new, + map(0x07, MINECRAFT_1_8), map(0x33, MINECRAFT_1_9), map(0x34, MINECRAFT_1_12), map(0x35, MINECRAFT_1_12_2), map(0x38, MINECRAFT_1_13)); CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new, + map(0x3D, MINECRAFT_1_8), map(0x38, MINECRAFT_1_9), map(0x3A, MINECRAFT_1_12), map(0x3B, MINECRAFT_1_12_1), map(0x3E, MINECRAFT_1_13)); CLIENTBOUND.register(ScoreboardObjective.class, ScoreboardObjective::new, + map(0x3B, MINECRAFT_1_8), map(0x3F, MINECRAFT_1_9), map(0x41, MINECRAFT_1_12), map(0x42, MINECRAFT_1_12_1), map(0x45, MINECRAFT_1_13)); CLIENTBOUND.register(ScoreboardTeam.class, ScoreboardTeam::new, + map(0x3E, MINECRAFT_1_8), map(0x41, MINECRAFT_1_9), map(0x43, MINECRAFT_1_12), map(0x44, MINECRAFT_1_12_1), map(0x47, MINECRAFT_1_13)); CLIENTBOUND.register(ScoreboardSetScore.class, ScoreboardSetScore::new, + map(0x3C, MINECRAFT_1_8), map(0x42, MINECRAFT_1_9), map(0x44, MINECRAFT_1_12), map(0x45, MINECRAFT_1_12_1), @@ -266,6 +280,7 @@ public enum StateRegistry { private static PacketMapping[] genericMappings(int id) { return new PacketMapping[]{ + map(id, MINECRAFT_1_8), map(id, MINECRAFT_1_9), map(id, MINECRAFT_1_12), map(id, MINECRAFT_1_13) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java index 74ca21bf8..cd5a92bab 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java @@ -80,7 +80,10 @@ public class ClientSettings implements MinecraftPacket { this.chatVisibility = ProtocolUtils.readVarInt(buf); this.chatColors = buf.readBoolean(); this.skinParts = buf.readUnsignedByte(); - this.mainHand = ProtocolUtils.readVarInt(buf); + + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { + this.mainHand = ProtocolUtils.readVarInt(buf); + } } @Override @@ -90,6 +93,9 @@ public class ClientSettings implements MinecraftPacket { ProtocolUtils.writeVarInt(buf, chatVisibility); buf.writeBoolean(chatColors); buf.writeByte(skinParts); - ProtocolUtils.writeVarInt(buf, mainHand); + + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { + ProtocolUtils.writeVarInt(buf, mainHand); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index 100950a74..69aaa92da 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -87,7 +87,11 @@ public class JoinGame implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { this.entityId = buf.readInt(); this.gamemode = buf.readUnsignedByte(); - this.dimension = buf.readInt(); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) { + this.dimension = buf.readInt(); + } else { + this.dimension = buf.readByte(); + } this.difficulty = buf.readUnsignedByte(); this.maxPlayers = buf.readUnsignedByte(); this.levelType = ProtocolUtils.readString(buf, 16); @@ -98,7 +102,11 @@ public class JoinGame implements MinecraftPacket { public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { buf.writeInt(entityId); buf.writeByte(gamemode); - buf.writeInt(dimension); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) { + buf.writeInt(dimension); + } else { + buf.writeByte(dimension); + } buf.writeByte(difficulty); buf.writeByte(maxPlayers); ProtocolUtils.writeString(buf, levelType); From 9ca1707df388c928cadb80300e21386363914d47 Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Mon, 6 Aug 2018 19:01:05 +0300 Subject: [PATCH 07/88] Fix unregisterConnection using non-lowercase username Fixes #18 --- .../src/main/java/com/velocitypowered/proxy/VelocityServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 630ad4ef8..4a5385e50 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -128,7 +128,7 @@ public class VelocityServer implements ProxyServer { } public void unregisterConnection(ConnectedPlayer connection) { - connectionsByName.remove(connection.getUsername(), connection); + connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection); connectionsByUuid.remove(connection.getUniqueId(), connection); } From ac378a8efab8fa1d3fed669f8e8044d8dcbf1b52 Mon Sep 17 00:00:00 2001 From: MatrixTunnel Date: Mon, 6 Aug 2018 16:06:57 -0700 Subject: [PATCH 08/88] Update version strings + add constant --- README.md | 2 +- .../proxy/connection/client/StatusSessionHandler.java | 3 ++- .../com/velocitypowered/proxy/protocol/ProtocolConstants.java | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e93376abf..d15197202 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ It is sufficient to run `./gradlew build` to run the full build cycle. ## Status Velocity is far from finished, but most of the essential pieces are in place: -you can switch between two servers running Minecraft 1.9-1.13. More versions +you can switch between two servers running Minecraft 1.8-1.13. More versions and functionality is planned. You should join us on **irc.spi.gt** `#velocity` or send us a pull request. 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 1dee26ce4..375185973 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,6 +4,7 @@ import com.google.common.base.Preconditions; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.VelocityConfiguration; 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; @@ -35,7 +36,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler { // Status request ServerPing ping = new ServerPing( - new ServerPing.Version(connection.getProtocolVersion(), "Velocity 1.9-1.13"), + new ServerPing.Version(connection.getProtocolVersion(), "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), new ServerPing.Players(0, configuration.getShowMaxPlayers()), configuration.getMotdComponent(), null diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index 421a04124..96c98f3d6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -17,6 +17,9 @@ public enum ProtocolConstants { ; public static final int MINECRAFT_1_13 = 393; public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8; + public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13; + + public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13"; public static final int[] SUPPORTED_VERSIONS = new int[] { MINECRAFT_1_8, From a95f0a66901eb9bd656da6ed6684c6b785afc932 Mon Sep 17 00:00:00 2001 From: theminecoder Date: Tue, 7 Aug 2018 14:11:51 +1000 Subject: [PATCH 09/88] Add discord status + link to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d15197202..11e91540a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Velocity [![Build Status](https://img.shields.io/jenkins/s/https/ci.velocitypowered.com/job/velocity/job/master.svg)](https://ci.velocitypowered.com/job/velocity/job/master/) +[![Join our Discord](https://img.shields.io/discord/472484458856185878.svg?logo=discord&label=)](https://discord.gg/8cB9Bgf) Velocity is a next-generation Minecraft: Java Edition proxy suite. It is designed specifically with mass-scale Minecraft in mind. From fdf5f27da60312d3944a4c8bd43a73af22b767e7 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 01:02:39 -0400 Subject: [PATCH 10/88] Improve server list ping, especially for legacy MC versions. --- .../api/proxy/ProxyServer.java | 6 ++ .../velocitypowered/proxy/VelocityServer.java | 5 ++ .../proxy/connection/MinecraftConnection.java | 11 +++- .../connection/MinecraftSessionHandler.java | 2 +- .../client/HandshakeSessionHandler.java | 29 ++++++++- .../client/LoginSessionHandler.java | 64 +++++++++++-------- .../client/StatusSessionHandler.java | 6 +- .../proxy/protocol/ProtocolConstants.java | 2 + .../protocol/netty/LegacyPingDecoder.java | 12 +++- .../protocol/netty/LegacyPingEncoder.java | 26 ++------ .../protocol/packet/LegacyDisconnect.java | 32 ++++++++++ .../protocol/packet/LegacyHandshake.java | 17 +++++ .../proxy/protocol/packet/LegacyPing.java | 15 ++++- 13 files changed, 169 insertions(+), 58 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java 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 7e23a0a07..1973482cf 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -32,6 +32,12 @@ public interface ProxyServer { */ Collection getAllPlayers(); + /** + * Returns the number of players currently connected to this proxy. + * @return the players on this proxy + */ + int getPlayerCount(); + /** * Retrieves a registered {@link ServerInfo} instance by its name. * @param name the name of the server diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 4a5385e50..a19213ad6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -149,6 +149,11 @@ public class VelocityServer implements ProxyServer { return ImmutableList.copyOf(connectionsByUuid.values()); } + @Override + public int getPlayerCount() { + return connectionsByUuid.size(); + } + @Override public Optional getServerInfo(@Nonnull String name) { Preconditions.checkNotNull(name, "name"); 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 a5375bf25..e829c008e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -6,6 +6,7 @@ import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.protocol.PacketWrapper; +import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipher; @@ -167,8 +168,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { public void setProtocolVersion(int protocolVersion) { this.protocolVersion = protocolVersion; - this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion); - this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion); + if (protocolVersion != ProtocolConstants.LEGACY) { + this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion); + this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion); + } else { + // Legacy handshake handling + this.channel.pipeline().remove(MINECRAFT_ENCODER); + this.channel.pipeline().remove(MINECRAFT_DECODER); + } } public MinecraftSessionHandler getSessionHandler() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index d445fb51c..4f13190de 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -4,7 +4,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import io.netty.buffer.ByteBuf; public interface MinecraftSessionHandler { - void handle(MinecraftPacket packet) throws Exception; + void handle(MinecraftPacket packet); default void handleUnknown(ByteBuf buf) { // No-op: we'll release the buffer later. 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 1b9ca2ba7..e16915f6d 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 @@ -1,14 +1,18 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; +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.data.ServerPing; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.packet.Disconnect; -import com.velocitypowered.proxy.protocol.packet.Handshake; +import com.velocitypowered.proxy.protocol.packet.*; +import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; +import net.kyori.text.format.TextColor; public class HandshakeSessionHandler implements MinecraftSessionHandler { private final MinecraftConnection connection; @@ -19,6 +23,12 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { + if (packet instanceof LegacyPing || packet instanceof LegacyHandshake) { + connection.setProtocolVersion(ProtocolConstants.LEGACY); + handleLegacy(packet); + return; + } + if (!(packet instanceof Handshake)) { throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName()); } @@ -43,6 +53,21 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { default: throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); } + } + private void handleLegacy(MinecraftPacket packet) { + if (packet instanceof LegacyPing) { + VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); + ServerPing ping = new ServerPing( + new ServerPing.Version(ProtocolConstants.MAXIMUM_GENERIC_VERSION, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), + new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()), + configuration.getMotdComponent(), + null + ); + // The disconnect packet is the same as the server response one. + connection.closeWith(LegacyDisconnect.fromPingResponse(LegacyPingResponse.from(ping))); + } else if (packet instanceof LegacyHandshake) { + connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED))); + } } } 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 d97bdb11f..cc28c91fa 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 @@ -19,6 +19,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; +import java.net.MalformedURLException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyPair; @@ -53,7 +54,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } @Override - public void handle(MinecraftPacket packet) throws Exception { + public void handle(MinecraftPacket packet) { if (packet instanceof LoginPluginResponse) { LoginPluginResponse lpr = (LoginPluginResponse) packet; if (lpr.getId() == playerInfoId && lpr.isSuccess()) { @@ -75,34 +76,41 @@ public class LoginSessionHandler implements MinecraftSessionHandler { handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); } } else if (packet instanceof EncryptionResponse) { - KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair(); - EncryptionResponse response = (EncryptionResponse) packet; - byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken()); - if (!Arrays.equals(verify, decryptedVerifyToken)) { - throw new IllegalStateException("Unable to successfully decrypt the verification token."); + try { + KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair(); + EncryptionResponse response = (EncryptionResponse) packet; + byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken()); + if (!Arrays.equals(verify, decryptedVerifyToken)) { + throw new IllegalStateException("Unable to successfully decrypt the verification token."); + } + + byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, response.getSharedSecret()); + String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); + + String playerIp = ((InetSocketAddress) inbound.getChannel().remoteAddress()).getHostString(); + VelocityServer.getServer().getHttpClient() + .get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp))) + .thenAcceptAsync(profileResponse -> { + try { + inbound.enableEncryption(decryptedSharedSecret); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + + GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class); + handleSuccessfulLogin(profile); + }, inbound.getChannel().eventLoop()) + .exceptionally(exception -> { + logger.error("Unable to enable encryption", exception); + inbound.close(); + return null; + }); + } catch (GeneralSecurityException e) { + logger.error("Unable to enable encryption", e); + inbound.close(); + } catch (MalformedURLException e) { + throw new AssertionError(e); } - - byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, response.getSharedSecret()); - String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); - - String playerIp = ((InetSocketAddress) inbound.getChannel().remoteAddress()).getHostString(); - VelocityServer.getServer().getHttpClient() - .get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp))) - .thenAcceptAsync(profileResponse -> { - try { - inbound.enableEncryption(decryptedSharedSecret); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - - GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class); - handleSuccessfulLogin(profile); - }, inbound.getChannel().eventLoop()) - .exceptionally(exception -> { - logger.error("Unable to enable encryption", exception); - inbound.close(); - return null; - }); } } 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 375185973..e75eafebd 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 @@ -35,9 +35,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler { VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); // Status request + int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection.getProtocolVersion() : + ProtocolConstants.MAXIMUM_GENERIC_VERSION; ServerPing ping = new ServerPing( - new ServerPing.Version(connection.getProtocolVersion(), "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), - new ServerPing.Players(0, configuration.getShowMaxPlayers()), + new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), + new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()), configuration.getMotdComponent(), null ); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index 96c98f3d6..4b61a1256 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -3,6 +3,8 @@ package com.velocitypowered.proxy.protocol; import java.util.Arrays; public enum ProtocolConstants { ; + public static final int LEGACY = -1; + public static final int MINECRAFT_1_8 = 47; public static final int MINECRAFT_1_9 = 107; public static final int MINECRAFT_1_9_1 = 108; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java index 46e41a519..153421386 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java @@ -1,7 +1,10 @@ package com.velocitypowered.proxy.protocol.netty; +import com.velocitypowered.proxy.protocol.PacketWrapper; +import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; import com.velocitypowered.proxy.protocol.packet.LegacyPing; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; @@ -18,9 +21,12 @@ public class LegacyPingDecoder extends ByteToMessageDecoder { short second = in.getUnsignedByte(in.readerIndex() + 1); if (first == 0xfe && second == 0x01) { in.skipBytes(in.readableBytes()); - out.add(new LegacyPing()); + out.add(new PacketWrapper(new LegacyPing(), Unpooled.EMPTY_BUFFER)); + } else if (first == 0x02) { + in.skipBytes(in.readableBytes()); + out.add(new PacketWrapper(new LegacyHandshake(), Unpooled.EMPTY_BUFFER)); + } else { + ctx.pipeline().remove(this); } - - ctx.pipeline().remove(this); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java index 6ef41fe51..3b31305e1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java @@ -1,39 +1,27 @@ package com.velocitypowered.proxy.protocol.netty; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.velocitypowered.proxy.protocol.packet.LegacyPingResponse; +import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import java.nio.charset.StandardCharsets; -import java.util.List; @ChannelHandler.Sharable -public class LegacyPingEncoder extends MessageToByteEncoder { +public class LegacyPingEncoder extends MessageToByteEncoder { public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder(); private LegacyPingEncoder() {} @Override - protected void encode(ChannelHandlerContext ctx, LegacyPingResponse msg, ByteBuf out) throws Exception { + protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out) throws Exception { out.writeByte(0xff); - String serializedResponse = serialize(msg); - out.writeShort(serializedResponse.length()); - out.writeBytes(serializedResponse.getBytes(StandardCharsets.UTF_16BE)); + writeLegacyString(out, msg.getReason()); } - private String serialize(LegacyPingResponse response) { - List parts = ImmutableList.of( - "§1", - Integer.toString(response.getProtocolVersion()), - response.getServerVersion(), - response.getMotd(), - Integer.toString(response.getPlayersOnline()), - Integer.toString(response.getPlayersMax()) - ); - return Joiner.on('\0').join(parts); + private static void writeLegacyString(ByteBuf out, String string) { + out.writeShort(string.length()); + out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE)); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java new file mode 100644 index 000000000..7a4ef0f0f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java @@ -0,0 +1,32 @@ +package com.velocitypowered.proxy.protocol.packet; + +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.ComponentSerializers; + +public class LegacyDisconnect { + private final String reason; + + public LegacyDisconnect(String reason) { + this.reason = reason; + } + + public static LegacyDisconnect fromPingResponse(LegacyPingResponse response) { + String kickMessage = String.join("\0", + "§1", + Integer.toString(response.getProtocolVersion()), + response.getServerVersion(), + response.getMotd(), + Integer.toString(response.getPlayersOnline()), + Integer.toString(response.getPlayersMax()) + ); + return new LegacyDisconnect(kickMessage); + } + + public static LegacyDisconnect from(TextComponent component) { + return new LegacyDisconnect(ComponentSerializers.LEGACY.serialize(component)); + } + + public String getReason() { + return reason; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java new file mode 100644 index 000000000..f6588674f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java @@ -0,0 +1,17 @@ +package com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import io.netty.buffer.ByteBuf; + +public class LegacyHandshake implements MinecraftPacket { + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java index ea75cd214..847160869 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java @@ -1,4 +1,17 @@ package com.velocitypowered.proxy.protocol.packet; -public class LegacyPing { +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import io.netty.buffer.ByteBuf; + +public class LegacyPing implements MinecraftPacket { + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } } From abbdf70d5e631a85e8e2cf80076d4e6d97000041 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 04:28:07 -0400 Subject: [PATCH 11/88] [Experimental] Remove PacketWrapper objects In both Velocity and BungeeCord, the most commonly created object is an object that encapsulates a Minecraft packet and a its associated byte data. At first, I considered trying to recycle these objects, but then I discovered that this object has no reason to exist, and actually somewhat complicates the implementation. Thus, this commit removes these objects, making Velocity more more GC-friendly by not allocating frequently-created objects. This is still an experimental change, but it's a fairly safe one to make. --- .../proxy/connection/MinecraftConnection.java | 19 +++++------- .../proxy/protocol/PacketWrapper.java | 30 ------------------- .../protocol/netty/LegacyPingDecoder.java | 6 ++-- .../protocol/netty/MinecraftDecoder.java | 6 ++-- 4 files changed, 12 insertions(+), 49 deletions(-) delete mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketWrapper.java 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 e829c008e..3582801a7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -5,12 +5,12 @@ import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.protocol.PacketWrapper; +import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipher; import com.velocitypowered.proxy.protocol.netty.*; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; @@ -79,18 +79,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof PacketWrapper) { - PacketWrapper pw = (PacketWrapper) msg; + if (msg instanceof MinecraftPacket) { + sessionHandler.handle((MinecraftPacket) msg); + } else if (msg instanceof ByteBuf) { try { - if (sessionHandler != null) { - if (pw.getPacket() == null) { - sessionHandler.handleUnknown(pw.getBuffer()); - } else { - sessionHandler.handle(pw.getPacket()); - } - } + sessionHandler.handleUnknown((ByteBuf) msg); } finally { - ReferenceCountUtil.release(pw.getBuffer()); + ReferenceCountUtil.release(msg); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketWrapper.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketWrapper.java deleted file mode 100644 index 5e1fa8bfb..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketWrapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.velocitypowered.proxy.protocol; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; - -public class PacketWrapper { - private final MinecraftPacket packet; - private final ByteBuf buffer; - - public PacketWrapper(MinecraftPacket packet, ByteBuf buffer) { - this.packet = packet; - this.buffer = buffer; - } - - public MinecraftPacket getPacket() { - return packet; - } - - public ByteBuf getBuffer() { - return buffer; - } - - @Override - public String toString() { - return "PacketWrapper{" + - "packet=" + packet + - ", buffer=" + ByteBufUtil.hexDump(buffer) + - '}'; - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java index 153421386..dfe396c7e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java @@ -1,10 +1,8 @@ package com.velocitypowered.proxy.protocol.netty; -import com.velocitypowered.proxy.protocol.PacketWrapper; import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; import com.velocitypowered.proxy.protocol.packet.LegacyPing; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; @@ -21,10 +19,10 @@ public class LegacyPingDecoder extends ByteToMessageDecoder { short second = in.getUnsignedByte(in.readerIndex() + 1); if (first == 0xfe && second == 0x01) { in.skipBytes(in.readableBytes()); - out.add(new PacketWrapper(new LegacyPing(), Unpooled.EMPTY_BUFFER)); + out.add(new LegacyPing()); } else if (first == 0x02) { in.skipBytes(in.readableBytes()); - out.add(new PacketWrapper(new LegacyHandshake(), Unpooled.EMPTY_BUFFER)); + out.add(new LegacyHandshake()); } else { ctx.pipeline().remove(this); } 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 d8768987a..f4baba7ac 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 @@ -26,13 +26,13 @@ public class MinecraftDecoder extends MessageToMessageDecoder { return; } - ByteBuf slice = msg.retainedSlice(); + ByteBuf slice = msg.slice(); int packetId = ProtocolUtils.readVarInt(msg); MinecraftPacket packet = this.protocolVersion.createPacket(packetId); if (packet == null) { msg.skipBytes(msg.readableBytes()); - out.add(new PacketWrapper(null, slice)); + out.add(slice.retain()); } else { try { packet.decode(msg, direction, protocolVersion.id); @@ -40,7 +40,7 @@ public class MinecraftDecoder extends MessageToMessageDecoder { throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction + " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId), e); } - out.add(new PacketWrapper(packet, slice)); + out.add(packet); } } From a2b4291b368c58e83c3f906a4bd43d4e639917e6 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 07:18:21 -0400 Subject: [PATCH 12/88] Use Guava immutable integer arrays. --- .../proxy/protocol/ProtocolConstants.java | 8 +++--- .../proxy/protocol/StateRegistry.java | 25 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index 4b61a1256..2944949b3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -1,6 +1,6 @@ package com.velocitypowered.proxy.protocol; -import java.util.Arrays; +import com.google.common.primitives.ImmutableIntArray; public enum ProtocolConstants { ; public static final int LEGACY = -1; @@ -23,7 +23,7 @@ public enum ProtocolConstants { ; public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13"; - public static final int[] SUPPORTED_VERSIONS = new int[] { + public static final ImmutableIntArray SUPPORTED_VERSIONS = ImmutableIntArray.of( MINECRAFT_1_8, MINECRAFT_1_9, MINECRAFT_1_9_1, @@ -36,10 +36,10 @@ public enum ProtocolConstants { ; MINECRAFT_1_12_1, MINECRAFT_1_12_2, MINECRAFT_1_13 - }; + ); public static boolean isSupported(int version) { - return Arrays.binarySearch(SUPPORTED_VERSIONS, version) >= 0; + return SUPPORTED_VERSIONS.contains(version); } public enum Direction { 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 3658126df..80d0d842c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -1,5 +1,6 @@ package com.velocitypowered.proxy.protocol; +import com.google.common.primitives.ImmutableIntArray; import com.velocitypowered.proxy.protocol.packet.*; import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectMap; @@ -144,13 +145,13 @@ public enum StateRegistry { public final PacketRegistry SERVERBOUND = new PacketRegistry(ProtocolConstants.Direction.SERVERBOUND, this); public static class PacketRegistry { - private static final IntObjectMap LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>(); + private static final IntObjectMap LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>(); static { - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9, new int[] { MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4 }); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, new int[] { MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1 }); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, new int[] { MINECRAFT_1_12_1 }); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, new int[] { MINECRAFT_1_12_2 }); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9, ImmutableIntArray.of(MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, ImmutableIntArray.of(MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, ImmutableIntArray.of(MINECRAFT_1_12_1)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, ImmutableIntArray.of(MINECRAFT_1_12_2)); } private final ProtocolConstants.Direction direction; @@ -160,10 +161,7 @@ public enum StateRegistry { public PacketRegistry(Direction direction, StateRegistry state) { this.direction = direction; this.state = state; - for (int version : ProtocolConstants.SUPPORTED_VERSIONS) { - versions.put(version, new ProtocolVersion(version)); - } - versions.put(MINIMUM_GENERIC_VERSION, new ProtocolVersion(MINIMUM_GENERIC_VERSION)); + ProtocolConstants.SUPPORTED_VERSIONS.forEach(version -> versions.put(version, new ProtocolVersion(version))); } public ProtocolVersion getVersion(final int version) { @@ -191,14 +189,15 @@ public enum StateRegistry { version.packetIdToSupplier.put(mapping.id, packetSupplier); version.packetClassToId.put(clazz, mapping.id); - int[] linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion); + ImmutableIntArray linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion); if (linked != null) { - links: for (int i : linked) { + links: for (int i = 0; i < linked.length(); i++) { + int linkedVersion = linked.get(i); // Make sure that later mappings override this one. for (PacketMapping m : mappings) { - if (i == m.protocolVersion) continue links; + if (linkedVersion == m.protocolVersion) continue links; } - register(clazz, packetSupplier, map(mapping.id, i)); + register(clazz, packetSupplier, map(mapping.id, linkedVersion)); } } } From b983cdb7b35a3077bdfdd9401162c9ac4a5ed109 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 07:18:54 -0400 Subject: [PATCH 13/88] Fix some spirous "connection closed" errors. --- .../proxy/connection/MinecraftConnection.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) 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 3582801a7..6d9738414 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -41,7 +41,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { private static final Logger logger = LogManager.getLogger(MinecraftConnection.class); private final Channel channel; - private boolean closed; private StateRegistry state; private MinecraftSessionHandler sessionHandler; private int protocolVersion; @@ -49,7 +48,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { public MinecraftConnection(Channel channel) { this.channel = channel; - this.closed = false; this.state = StateRegistry.HANDSHAKE; } @@ -73,8 +71,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { if (association != null) { logger.info("{} has disconnected", association); } - - teardown(); } @Override @@ -103,7 +99,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause); } - closed = true; ctx.close(); } } @@ -124,19 +119,15 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } public void closeWith(Object msg) { - ensureOpen(); - teardown(); - channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); + if (channel.isActive()) { + channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); + } } public void close() { - ensureOpen(); - teardown(); - channel.close(); - } - - public void teardown() { - closed = true; + if (channel.isActive()) { + channel.close(); + } } public Channel getChannel() { @@ -144,7 +135,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } public boolean isClosed() { - return closed; + return !channel.isActive(); } public StateRegistry getState() { @@ -186,10 +177,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } private void ensureOpen() { - Preconditions.checkState(!closed, "Connection is closed."); + Preconditions.checkState(!isClosed(), "Connection is closed."); } public void setCompressionThreshold(int threshold) { + ensureOpen(); + if (threshold == -1) { channel.pipeline().remove(COMPRESSION_DECODER); channel.pipeline().remove(COMPRESSION_ENCODER); @@ -206,6 +199,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } public void enableEncryption(byte[] secret) throws GeneralSecurityException { + ensureOpen(); + SecretKey key = new SecretKeySpec(secret, "AES"); VelocityCipherFactory factory = Natives.cipher.get(); From fe79c6617123bdb63b860f8289e7879c1e3a8a62 Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Tue, 7 Aug 2018 14:32:22 +0300 Subject: [PATCH 14/88] GS4 Query handler (#20) --- .../network/ConnectionManager.java | 29 ++- .../velocitypowered/proxy/VelocityServer.java | 5 +- .../proxy/config/VelocityConfiguration.java | 21 +- .../proxy/protocol/netty/GS4QueryHandler.java | 189 ++++++++++++++++++ proxy/src/main/resources/velocity.toml | 9 +- 5 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java diff --git a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java index 9b8676db8..851eda878 100644 --- a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java @@ -5,14 +5,13 @@ 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 org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; @@ -21,15 +20,20 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; 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.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; import java.util.HashSet; @@ -54,6 +58,7 @@ public final class ConnectionManager { private final Set endpoints = new HashSet<>(); private final Class serverSocketChannelClass; private final Class socketChannelClass; + private final Class datagramChannelClass; private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; @@ -62,11 +67,13 @@ public final class ConnectionManager { if (epoll) { this.serverSocketChannelClass = EpollServerSocketChannel.class; this.socketChannelClass = EpollSocketChannel.class; + this.datagramChannelClass = EpollDatagramChannel.class; this.bossGroup = new EpollEventLoopGroup(0, createThreadFactory("Netty Epoll Boss #%d")); this.workerGroup = new EpollEventLoopGroup(0, createThreadFactory("Netty Epoll Worker #%d")); } else { this.serverSocketChannelClass = NioServerSocketChannel.class; this.socketChannelClass = NioSocketChannel.class; + this.datagramChannelClass = NioDatagramChannel.class; this.bossGroup = new NioEventLoopGroup(0, createThreadFactory("Netty Nio Boss #%d")); this.workerGroup = new NioEventLoopGroup(0, createThreadFactory("Netty Nio Worker #%d")); } @@ -120,6 +127,24 @@ public final class ConnectionManager { }); } + public void queryBind(final String hostname, final int port) { + new Bootstrap() + .channel(datagramChannelClass) + .group(this.workerGroup) + .handler(new GS4QueryHandler()) + .localAddress(hostname, port) + .bind() + .addListener((ChannelFutureListener) future -> { + final Channel channel = future.channel(); + if (future.isSuccess()) { + this.endpoints.add(channel); + logger.info("Listening for GS4 query on {}", channel.localAddress()); + } else { + logger.error("Can't bind to {}", channel.localAddress(), future.cause()); + } + }); + } + public Bootstrap createWorker() { return new Bootstrap() .channel(this.socketChannelClass) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index a19213ad6..abc570553 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -9,7 +9,6 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.network.ConnectionManager; import com.velocitypowered.proxy.config.VelocityConfiguration; -import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.api.server.ServerInfo; @@ -97,6 +96,10 @@ public class VelocityServer implements ProxyServer { httpClient = new NettyHttpClient(this); this.cm.bind(configuration.getBind()); + + if (configuration.isQueryEnabled()) { + this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort()); + } } public ServerMap getServers() { 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 5e3959246..10f697903 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -32,12 +32,15 @@ public class VelocityConfiguration { private final int compressionThreshold; private final int compressionLevel; + private final boolean queryEnabled; + private final int queryPort; + private Component motdAsComponent; private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, IPForwardingMode ipForwardingMode, Map servers, List attemptConnectionOrder, int compressionThreshold, - int compressionLevel) { + int compressionLevel, boolean queryEnabled, int queryPort) { this.bind = bind; this.motd = motd; this.showMaxPlayers = showMaxPlayers; @@ -47,6 +50,8 @@ public class VelocityConfiguration { this.attemptConnectionOrder = attemptConnectionOrder; this.compressionThreshold = compressionThreshold; this.compressionLevel = compressionLevel; + this.queryEnabled = queryEnabled; + this.queryPort = queryPort; } public boolean validate() { @@ -126,6 +131,14 @@ public class VelocityConfiguration { return AddressUtil.parseAddress(bind); } + public boolean isQueryEnabled() { + return queryEnabled; + } + + public int getQueryPort() { + return queryPort; + } + public String getMotd() { return motd; } @@ -181,6 +194,8 @@ public class VelocityConfiguration { ", attemptConnectionOrder=" + attemptConnectionOrder + ", compressionThreshold=" + compressionThreshold + ", compressionLevel=" + compressionLevel + + ", queryEnabled=" + queryEnabled + + ", queryPort=" + queryPort + ", motdAsComponent=" + motdAsComponent + '}'; } @@ -209,7 +224,9 @@ public class VelocityConfiguration { ImmutableMap.copyOf(servers), toml.getTable("servers").getList("try"), toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(), - toml.getTable("advanced").getLong("compression-level", -1L).intValue()); + toml.getTable("advanced").getLong("compression-level", -1L).intValue(), + toml.getTable("query").getBoolean("enabled"), + toml.getTable("query").getLong("port", 25577L).intValue()); } } } 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 new file mode 100644 index 000000000..7c7608597 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -0,0 +1,189 @@ +package com.velocitypowered.proxy.protocol.netty; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableSet; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +public class GS4QueryHandler extends SimpleChannelInboundHandler { + private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class); + + private final static short QUERY_MAGIC_FIRST = 0xFE; + private final static short QUERY_MAGIC_SECOND = 0xFD; + private final static byte QUERY_TYPE_HANDSHAKE = 0x09; + private final static byte QUERY_TYPE_STAT = 0x00; + private final static byte[] QUERY_RESPONSE_FULL_PADDING = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, (byte) 0x80, 0x00 }; + private final static byte[] QUERY_RESPONSE_FULL_PADDING2 = new byte[] { 0x01, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00 }; + + // Contents to add into basic stat response. See ResponseWriter class below + private final static Set QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of( + "hostname", + "gametype", + "map", + "numplayers", + "maxplayers", + "hostport", + "hostip" + ); + + private final static Cache sessions = CacheBuilder.newBuilder() + .expireAfterWrite(30, TimeUnit.SECONDS) + .build(); + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { + ByteBuf queryMessage = msg.content(); + InetAddress senderAddress = msg.sender().getAddress(); + + // Verify query packet magic + if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST && queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) { + throw new IllegalStateException("Invalid query packet magic"); + } + + // Read packet header + short type = queryMessage.readUnsignedByte(); + int sessionId = queryMessage.readInt(); + + // Allocate buffer for response + ByteBuf queryResponse = ctx.alloc().buffer(); + DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender()); + + switch(type) { + case QUERY_TYPE_HANDSHAKE: { + // Generate new challenge token and put it into the sessions cache + int challengeToken = ThreadLocalRandom.current().nextInt(); + sessions.put(senderAddress, challengeToken); + + // Respond with challenge token + queryResponse.writeByte(QUERY_TYPE_HANDSHAKE); + queryResponse.writeInt(sessionId); + writeString(queryResponse, Integer.toString(challengeToken)); + break; + } + + case QUERY_TYPE_STAT: { + // Check if query was done with session previously generated using a handshake packet + int challengeToken = queryMessage.readInt(); + Integer session = sessions.getIfPresent(senderAddress); + if (session == null || session != challengeToken) { + throw new IllegalStateException("Invalid challenge token"); + } + + // Check which query response client expects + if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) { + throw new IllegalStateException("Invalid query packet"); + } + + // Packet header + queryResponse.writeByte(QUERY_TYPE_STAT); + queryResponse.writeInt(sessionId); + + // Fetch information + VelocityServer server = VelocityServer.getServer(); + Collection players = server.getAllPlayers(); + + // Start writing the response + ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0); + responseWriter.write("hostname", server.getConfiguration().getMotd()); + responseWriter.write("gametype", "SMP"); + + responseWriter.write("game_id", "MINECRAFT"); + responseWriter.write("version", ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING); + responseWriter.write("plugins", ""); + + responseWriter.write("map", "Velocity"); + responseWriter.write("numplayers", players.size()); + responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers()); + responseWriter.write("hostport", server.getConfiguration().getBind().getPort()); + responseWriter.write("hostip", server.getConfiguration().getBind().getHostString()); + + responseWriter.writePlayers(players); + break; + } + + default: { + throw new IllegalStateException("Invalid query type: " + type); + } + } + + // Send the response + ctx.writeAndFlush(responsePacket); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.warn("Error while trying to handle a query packet from {}", ctx.channel().remoteAddress(), cause); + } + + private static void writeString(ByteBuf buf, String string) { + buf.writeCharSequence(string, StandardCharsets.ISO_8859_1); + buf.writeByte(0x00); + } + + private static class ResponseWriter { + private final ByteBuf buf; + private final boolean isBasic; + + ResponseWriter(ByteBuf buf, boolean isBasic) { + this.buf = buf; + this.isBasic = isBasic; + + if (!isBasic) { + buf.writeBytes(QUERY_RESPONSE_FULL_PADDING); + } + } + + // Writes k/v to stat packet body if this writer is initialized + // for full stat response. Otherwise this follows + // GS4QueryHandler#QUERY_BASIC_RESPONSE_CONTENTS to decide what + // to write into packet body + void write(String key, Object value) { + if (isBasic) { + // Basic contains only specific set of data + if (!QUERY_BASIC_RESPONSE_CONTENTS.contains(key)) { + return; + } + + // Special case hostport + if (key.equals("hostport")) { + buf.writeShortLE((Integer) value); + } else { + writeString(buf, value.toString()); + } + } else { + writeString(buf, key); + writeString(buf, value.toString()); + } + } + + // Ends packet k/v body writing and writes stat player list to + // the packet if this writer is initialized for full stat response + void writePlayers(Collection players) { + if (isBasic) { + return; + } + + // Ends the full stat key-value body with \0 + buf.writeByte(0x00); + + buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2); + players.forEach(player -> writeString(buf, player.getUsername())); + buf.writeByte(0x00); + } + } +} diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml index cb4c6fbb5..18aac1e6d 100644 --- a/proxy/src/main/resources/velocity.toml +++ b/proxy/src/main/resources/velocity.toml @@ -38,4 +38,11 @@ try = [ compression-threshold = 1024 # How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6. -compression-level = -1 \ No newline at end of file +compression-level = -1 + +[query] +# Whether to enable responding to GameSpy 4 query responses or not +enabled = false + +# If query responding is enabled, on what port should query response listener listen on? +port = 25577 \ No newline at end of file From 3e0df79c986a6cc1a0717ec30808ed410ee602d3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 07:34:41 -0400 Subject: [PATCH 15/88] Fix GS4 "can't bind to null" error. --- .../com/velocitypowered/network/ConnectionManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java index 851eda878..ab5853bc3 100644 --- a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java @@ -128,19 +128,19 @@ public final class ConnectionManager { } public void queryBind(final String hostname, final int port) { - new Bootstrap() + Bootstrap bootstrap = new Bootstrap() .channel(datagramChannelClass) .group(this.workerGroup) .handler(new GS4QueryHandler()) - .localAddress(hostname, port) - .bind() + .localAddress(hostname, port); + bootstrap.bind() .addListener((ChannelFutureListener) future -> { final Channel channel = future.channel(); if (future.isSuccess()) { this.endpoints.add(channel); logger.info("Listening for GS4 query on {}", channel.localAddress()); } else { - logger.error("Can't bind to {}", channel.localAddress(), future.cause()); + logger.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause()); } }); } From b5fcc1b34af62d686098dd6914a08f5af5119b1b Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 07:51:09 -0400 Subject: [PATCH 16/88] Fix GS4 issues. --- .../proxy/protocol/netty/GS4QueryHandler.java | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) 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 7c7608597..1848ad3fb 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 @@ -50,84 +50,84 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler ByteBuf queryMessage = msg.content(); InetAddress senderAddress = msg.sender().getAddress(); - // Verify query packet magic - if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST && queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) { - throw new IllegalStateException("Invalid query packet magic"); - } - - // Read packet header - short type = queryMessage.readUnsignedByte(); - int sessionId = queryMessage.readInt(); - // Allocate buffer for response ByteBuf queryResponse = ctx.alloc().buffer(); DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender()); - switch(type) { - case QUERY_TYPE_HANDSHAKE: { - // Generate new challenge token and put it into the sessions cache - int challengeToken = ThreadLocalRandom.current().nextInt(); - sessions.put(senderAddress, challengeToken); - - // Respond with challenge token - queryResponse.writeByte(QUERY_TYPE_HANDSHAKE); - queryResponse.writeInt(sessionId); - writeString(queryResponse, Integer.toString(challengeToken)); - break; + try { + // Verify query packet magic + if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST || queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) { + throw new IllegalStateException("Invalid query packet magic"); } - case QUERY_TYPE_STAT: { - // Check if query was done with session previously generated using a handshake packet - int challengeToken = queryMessage.readInt(); - Integer session = sessions.getIfPresent(senderAddress); - if (session == null || session != challengeToken) { - throw new IllegalStateException("Invalid challenge token"); + // Read packet header + short type = queryMessage.readUnsignedByte(); + int sessionId = queryMessage.readInt(); + + switch (type) { + case QUERY_TYPE_HANDSHAKE: { + // Generate new challenge token and put it into the sessions cache + int challengeToken = ThreadLocalRandom.current().nextInt(); + sessions.put(senderAddress, challengeToken); + + // Respond with challenge token + queryResponse.writeByte(QUERY_TYPE_HANDSHAKE); + queryResponse.writeInt(sessionId); + writeString(queryResponse, Integer.toString(challengeToken)); + break; } - // Check which query response client expects - if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) { - throw new IllegalStateException("Invalid query packet"); + case QUERY_TYPE_STAT: { + // Check if query was done with session previously generated using a handshake packet + int challengeToken = queryMessage.readInt(); + Integer session = sessions.getIfPresent(senderAddress); + if (session == null || session != challengeToken) { + throw new IllegalStateException("Invalid challenge token"); + } + + // Check which query response client expects + if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) { + throw new IllegalStateException("Invalid query packet"); + } + + // Packet header + queryResponse.writeByte(QUERY_TYPE_STAT); + queryResponse.writeInt(sessionId); + + // Fetch information + VelocityServer server = VelocityServer.getServer(); + Collection players = server.getAllPlayers(); + + // Start writing the response + ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0); + responseWriter.write("hostname", server.getConfiguration().getMotd()); + responseWriter.write("gametype", "SMP"); + + responseWriter.write("game_id", "MINECRAFT"); + responseWriter.write("version", ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING); + responseWriter.write("plugins", ""); + + responseWriter.write("map", "Velocity"); + responseWriter.write("numplayers", players.size()); + responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers()); + responseWriter.write("hostport", server.getConfiguration().getBind().getPort()); + responseWriter.write("hostip", server.getConfiguration().getBind().getHostString()); + + responseWriter.writePlayers(players); + break; } - // Packet header - queryResponse.writeByte(QUERY_TYPE_STAT); - queryResponse.writeInt(sessionId); - - // Fetch information - VelocityServer server = VelocityServer.getServer(); - Collection players = server.getAllPlayers(); - - // Start writing the response - ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0); - responseWriter.write("hostname", server.getConfiguration().getMotd()); - responseWriter.write("gametype", "SMP"); - - responseWriter.write("game_id", "MINECRAFT"); - responseWriter.write("version", ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING); - responseWriter.write("plugins", ""); - - responseWriter.write("map", "Velocity"); - responseWriter.write("numplayers", players.size()); - responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers()); - responseWriter.write("hostport", server.getConfiguration().getBind().getPort()); - responseWriter.write("hostip", server.getConfiguration().getBind().getHostString()); - - responseWriter.writePlayers(players); - break; + default: { + throw new IllegalStateException("Invalid query type: " + type); + } } - default: { - throw new IllegalStateException("Invalid query type: " + type); - } + // Send the response + ctx.writeAndFlush(responsePacket); + } catch (Exception e) { + logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e); + responsePacket.release(); } - - // Send the response - ctx.writeAndFlush(responsePacket); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - logger.warn("Error while trying to handle a query packet from {}", ctx.channel().remoteAddress(), cause); } private static void writeString(ByteBuf buf, String string) { From 09eff5a2fb1b2684e08d2d8a6eb46b1b855012c3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 07:56:28 -0400 Subject: [PATCH 17/88] GS4 clients generally don't understand MC color codes (or JSON chat). --- .../velocitypowered/proxy/protocol/netty/GS4QueryHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 1848ad3fb..b0c5a838b 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 @@ -10,6 +10,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; +import net.kyori.text.serializer.ComponentSerializers; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -100,7 +101,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler // Start writing the response ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0); - responseWriter.write("hostname", server.getConfiguration().getMotd()); + responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent())); responseWriter.write("gametype", "SMP"); responseWriter.write("game_id", "MINECRAFT"); From 05693425bfccd06da7b7c01c7a6d5bce09043b68 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 09:34:31 -0400 Subject: [PATCH 18/88] Add a basic command handling framework inspired by Bukkit/BungeeCord. This doesn't yet support tab complete, that will come later. Additionally, a /server command (using your configuration) and /velocity (shows basic copyright information about the proxy) have been added. --- README.md | 9 ++++ .../api/command/CommandExecutor.java | 29 +++++++++++ .../api/command/CommandInvoker.java | 23 +++++++++ .../api/proxy/InboundConnection.java | 26 ++++++++++ .../com/velocitypowered/api/proxy/Player.java | 16 +------ .../api/proxy/ProxyServer.java | 9 ++++ .../velocitypowered/proxy/VelocityServer.java | 29 +++++++++++ .../proxy/command/CommandManager.java | 48 +++++++++++++++++++ .../proxy/command/ServerCommand.java | 41 ++++++++++++++++ .../proxy/command/VelocityCommand.java | 42 ++++++++++++++++ .../client/ClientPlaySessionHandler.java | 23 +++++---- .../connection/client/ConnectedPlayer.java | 10 ++++ .../client/HandshakeSessionHandler.java | 26 ++++++++++ 13 files changed, 309 insertions(+), 22 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java create mode 100644 api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java diff --git a/README.md b/README.md index 11e91540a..018884003 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,15 @@ wrapper script (`./gradlew`) as our CI builds using it. It is sufficient to run `./gradlew build` to run the full build cycle. +## Running + +Once you've built Velocity, you can copy and run the `-all` JAR from +`proxy/build/libs`. Velocity will generate a default configuration file +and you can configure it from there. + +Alternatively, you can get the proxy JAR from the [downloads](https://www.velocitypowered.com/downloads) +page. + ## Status Velocity is far from finished, but most of the essential pieces are in place: diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java b/api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java new file mode 100644 index 000000000..22c6eb765 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java @@ -0,0 +1,29 @@ +package com.velocitypowered.api.command; + +import com.google.common.collect.ImmutableList; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Represents a command that can be executed by a {@link CommandInvoker}, such as a {@link com.velocitypowered.api.proxy.Player} + * or the console. + */ +public interface CommandExecutor { + /** + * Executes the command for the specified {@link CommandInvoker}. + * @param invoker the invoker of this command + * @param args the arguments for this command + */ + void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args); + + /** + * Provides tab complete suggestions for a command for a specified {@link CommandInvoker}. + * @param invoker the invoker to run the command for + * @param currentArgs the current, partial arguments for this command + * @return tab complete suggestions + */ + default List suggest(@Nonnull CommandInvoker invoker, @Nonnull String[] currentArgs) { + return ImmutableList.of(); + } +} diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java b/api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java new file mode 100644 index 000000000..22230dcb9 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java @@ -0,0 +1,23 @@ +package com.velocitypowered.api.command; + +import net.kyori.text.Component; + +import javax.annotation.Nonnull; + +/** + * Represents something that can be used to run a {@link CommandExecutor}. + */ +public interface CommandInvoker { + /** + * Sends the specified {@code component} to the invoker. + * @param component the text component to send + */ + void sendMessage(@Nonnull Component component); + + /** + * Determines whether or not the invoker has a particular permission. + * @param permission the permission to check for + * @return whether or not the invoker has permission to run this command + */ + boolean hasPermission(@Nonnull String permission); +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java new file mode 100644 index 000000000..1385b594b --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -0,0 +1,26 @@ +package com.velocitypowered.api.proxy; + +import java.net.InetSocketAddress; + +/** + * Represents a connection to the proxy. There is no guarantee that the connection has been fully initialized. + */ +public interface InboundConnection { + /** + * Returns the player's IP address. + * @return the player's IP + */ + InetSocketAddress getRemoteAddress(); + + /** + * Determine whether or not the player remains online. + * @return whether or not the player active + */ + boolean isActive(); + + /** + * Returns the current protocol version this connection uses. + * @return the protocol version the connection uses + */ + int getProtocolVersion(); +} 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 f2f233459..89e4df3a6 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,18 +1,18 @@ package com.velocitypowered.api.proxy; +import com.velocitypowered.api.command.CommandInvoker; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.api.util.MessagePosition; import net.kyori.text.Component; import javax.annotation.Nonnull; -import java.net.InetSocketAddress; import java.util.Optional; import java.util.UUID; /** * Represents a player who is connected to the proxy. */ -public interface Player { +public interface Player extends CommandInvoker, InboundConnection { /** * Returns the player's current username. * @return the username @@ -31,18 +31,6 @@ public interface Player { */ Optional getCurrentServer(); - /** - * Returns the player's IP address. - * @return the player's IP - */ - InetSocketAddress getRemoteAddress(); - - /** - * Determine whether or not the player remains online. - * @return whether or not the player active - */ - boolean isActive(); - /** * Sends a chat message to the player's client. * @param component the chat message to send 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 1973482cf..1dbb4b580 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -1,5 +1,6 @@ package com.velocitypowered.api.proxy; +import com.velocitypowered.api.command.CommandInvoker; import com.velocitypowered.api.server.ServerInfo; import javax.annotation.Nonnull; @@ -62,4 +63,12 @@ public interface ProxyServer { * @param server the server to unregister */ void unregisterServer(@Nonnull ServerInfo server); + + /** + * Returns an instance of {@link CommandInvoker} that can be used to determine if the command is being invoked by + * the console or a console-like executor. Plugins that execute commands are strongly urged to implement their own + * {@link CommandInvoker} instead of using the console invoker. + * @return the console command invoker + */ + CommandInvoker getConsoleCommandInvoker(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index abc570553..a6f365b22 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -4,19 +4,24 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.velocitypowered.api.command.CommandInvoker; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.network.ConnectionManager; +import com.velocitypowered.proxy.command.ServerCommand; +import com.velocitypowered.proxy.command.VelocityCommand; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.proxy.command.CommandManager; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.ServerMap; import io.netty.bootstrap.Bootstrap; import net.kyori.text.Component; +import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.GsonComponentSerializer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -43,11 +48,26 @@ public class VelocityServer implements ProxyServer { private NettyHttpClient httpClient; private KeyPair serverKeyPair; private final ServerMap servers = new ServerMap(); + private final CommandManager commandManager = new CommandManager(); private final Map connectionsByUuid = new ConcurrentHashMap<>(); private final Map connectionsByName = new ConcurrentHashMap<>(); + private final CommandInvoker consoleCommandInvoker = new CommandInvoker() { + @Override + public void sendMessage(@Nonnull Component component) { + // TODO: TerminalConsoleAppender + logger.info(ComponentSerializers.PLAIN.serialize(component)); + } + + @Override + public boolean hasPermission(@Nonnull String permission) { + return true; + } + }; private VelocityServer() { + commandManager.registerCommand("velocity", new VelocityCommand()); + commandManager.registerCommand("server", new ServerCommand()); } public static VelocityServer getServer() { @@ -62,6 +82,10 @@ public class VelocityServer implements ProxyServer { return configuration; } + public CommandManager getCommandManager() { + return commandManager; + } + public void start() { logger.info("Using {}", Natives.compressor.getLoadedVariant()); logger.info("Using {}", Natives.cipher.getLoadedVariant()); @@ -177,4 +201,9 @@ public class VelocityServer implements ProxyServer { public void unregisterServer(@Nonnull ServerInfo server) { servers.unregister(server); } + + @Override + public CommandInvoker getConsoleCommandInvoker() { + return consoleCommandInvoker; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java new file mode 100644 index 000000000..5a3753089 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java @@ -0,0 +1,48 @@ +package com.velocitypowered.proxy.command; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.command.CommandExecutor; +import com.velocitypowered.api.command.CommandInvoker; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class CommandManager { + private final Map executors = new HashMap<>(); + + public void registerCommand(String name, CommandExecutor executor) { + Preconditions.checkNotNull(name, "name"); + Preconditions.checkNotNull(executor, "executor"); + this.executors.put(name, executor); + } + + public void unregisterCommand(String name) { + Preconditions.checkNotNull(name, "name"); + this.executors.remove(name); + } + + public boolean execute(CommandInvoker invoker, String cmdLine) { + Preconditions.checkNotNull(invoker, "invoker"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); + + String[] split = cmdLine.split(" ", -1); + if (split.length == 0) { + return false; + } + + String command = split[0]; + String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); + CommandExecutor executor = executors.get(command); + if (executor == null) { + return false; + } + + try { + executor.execute(invoker, actualArgs); + return true; + } catch (Exception e) { + throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + invoker, e); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java new file mode 100644 index 000000000..bb66832ca --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -0,0 +1,41 @@ +package com.velocitypowered.proxy.command; + +import com.velocitypowered.api.command.CommandExecutor; +import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.proxy.VelocityServer; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +import javax.annotation.Nonnull; +import java.util.Optional; +import java.util.stream.Collectors; + +public class ServerCommand implements CommandExecutor { + @Override + public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) { + if (!(invoker instanceof Player)) { + invoker.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED)); + return; + } + + Player player = (Player) invoker; + if (args.length == 1) { + // Trying to connect to a server. + String serverName = args[0]; + Optional server = VelocityServer.getServer().getServerInfo(serverName); + if (!server.isPresent()) { + player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); + return; + } + + player.createConnectionRequest(server.get()).fireAndForget(); + } else { + String serverList = VelocityServer.getServer().getAllServers().stream() + .map(ServerInfo::getName) + .collect(Collectors.joining(", ")); + player.sendMessage(TextComponent.of("Available servers: " + serverList, TextColor.YELLOW)); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java new file mode 100644 index 000000000..20b77e6bc --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -0,0 +1,42 @@ +package com.velocitypowered.proxy.command; + +import com.velocitypowered.api.command.CommandExecutor; +import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.proxy.VelocityServer; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.format.TextColor; + +import javax.annotation.Nonnull; + +public class VelocityCommand implements CommandExecutor { + @Override + public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) { + String implVersion = VelocityServer.class.getPackage().getImplementationVersion(); + TextComponent thisIsVelocity = TextComponent.builder() + .content("This is ") + .append(TextComponent.of("Velocity " + implVersion, TextColor.DARK_AQUA)) + .append(TextComponent.of(", the next generation Minecraft: Java Edition proxy.", TextColor.WHITE)) + .build(); + TextComponent velocityInfo = TextComponent.builder() + .content("Copyright 2018 Velocity Contributors. Velocity is freely licensed under the terms of the " + + "MIT License.") + .build(); + TextComponent velocityWebsite = TextComponent.builder() + .content("Visit the ") + .append(TextComponent.builder("Velocity website") + .color(TextColor.GREEN) + .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com")) + .build()) + .append(TextComponent.of(" or the ", TextColor.WHITE)) + .append(TextComponent.builder("Velocity GitHub") + .color(TextColor.GREEN) + .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/astei/velocity")) + .build()) + .build(); + + invoker.sendMessage(thisIsVelocity); + invoker.sendMessage(velocityInfo); + invoker.sendMessage(velocityWebsite); + } +} 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 bc9c11986..cecc1c80f 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,7 +1,7 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.connection.backend.ServerConnection; +import com.velocitypowered.proxy.command.VelocityCommand; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.data.scoreboard.Objective; import com.velocitypowered.proxy.data.scoreboard.Score; @@ -9,16 +9,13 @@ import com.velocitypowered.proxy.data.scoreboard.Scoreboard; import com.velocitypowered.proxy.data.scoreboard.Team; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; -import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.remap.EntityIdRemapper; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.util.ThrowableUtils; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.EventLoop; -import io.netty.util.ReferenceCountUtil; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import org.apache.logging.log4j.LogManager; @@ -81,11 +78,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (packet instanceof Chat) { Chat chat = (Chat) packet; - if (chat.getMessage().equals("/connect")) { - ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25566)); - player.createConnectionRequest(info).fireAndForget(); - return; + String msg = ((Chat) packet).getMessage(); + if (msg.startsWith("/")) { + try { + if (!VelocityServer.getServer().getCommandManager().execute(player, msg.substring(1))) { + player.getConnectedServer().getMinecraftConnection().write(msg); + } + } catch (Exception e) { + logger.info("Exception occurred while running command for {}", player.getProfile().getName(), e); + player.sendMessage(TextComponent.of("An error occurred while running this command.", TextColor.RED)); + return; + } + } else { + player.getConnectedServer().getMinecraftConnection().write(chat); } + return; } if (packet instanceof PluginMessage) { 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 dda03be7e..a10681fa8 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 @@ -83,6 +83,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return connection.getChannel().isActive(); } + @Override + public int getProtocolVersion() { + return connection.getProtocolVersion(); + } + @Override public void sendMessage(@Nonnull Component component, @Nonnull MessagePosition position) { Preconditions.checkNotNull(component, "component"); @@ -217,6 +222,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return "[connected player] " + getProfile().getName() + " (" + getRemoteAddress() + ")"; } + @Override + public boolean hasPermission(@Nonnull String permission) { + return false; // TODO: Implement permissions. + } + private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final ServerInfo info; 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 e16915f6d..700709867 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 @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; @@ -14,6 +15,8 @@ import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; +import java.net.InetSocketAddress; + public class HandshakeSessionHandler implements MinecraftSessionHandler { private final MinecraftConnection connection; @@ -70,4 +73,27 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED))); } } + + private static class InitialInboundConnection implements InboundConnection { + private final MinecraftConnection connection; + + private InitialInboundConnection(MinecraftConnection connection) { + this.connection = connection; + } + + @Override + public InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) connection.getChannel().remoteAddress(); + } + + @Override + public boolean isActive() { + return connection.getChannel().isActive(); + } + + @Override + public int getProtocolVersion() { + return connection.getProtocolVersion(); + } + } } From a7a227ed0553bcbf338ab1ff9b7b0603307d0478 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 10:14:34 -0400 Subject: [PATCH 19/88] Add shutdown flag. --- .../java/com/velocitypowered/proxy/VelocityServer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index a6f365b22..43c0365ec 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -49,6 +49,7 @@ public class VelocityServer implements ProxyServer { private KeyPair serverKeyPair; private final ServerMap servers = new ServerMap(); private final CommandManager commandManager = new CommandManager(); + private boolean shutdown = false; private final Map connectionsByUuid = new ConcurrentHashMap<>(); private final Map connectionsByName = new ConcurrentHashMap<>(); @@ -134,7 +135,16 @@ public class VelocityServer implements ProxyServer { return this.cm.createWorker(); } + public boolean isShutdown() { + return shutdown; + } + public void shutdown() { + Preconditions.checkState(!shutdown, "Server already shut down"); + shutdown = true; + + logger.info("Shutting down the proxy..."); + this.cm.shutdown(); } From a044ed72dea74fc9a57bafabc12d82aeebe728e6 Mon Sep 17 00:00:00 2001 From: Minecrell Date: Tue, 7 Aug 2018 16:24:01 +0200 Subject: [PATCH 20/88] Add TerminalConsoleAppender for console prompts --- proxy/build.gradle | 10 ++++- .../com/velocitypowered/proxy/Velocity.java | 7 ++-- .../velocitypowered/proxy/VelocityServer.java | 3 +- .../proxy/console/VelocityConsole.java | 39 +++++++++++++++++++ .../resources/log4j2.component.properties | 1 + proxy/src/main/resources/log4j2.xml | 10 ++--- 6 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java create mode 100644 proxy/src/main/resources/log4j2.component.properties diff --git a/proxy/build.gradle b/proxy/build.gradle index 32632575e..ec0a4f7e9 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'com.github.johnrengelman.shadow' version '2.0.4' + id 'de.sebastianboegl.shadow.transformer.log4j' version '2.1.1' } compileJava { @@ -21,17 +22,24 @@ jar { dependencies { compile project(':velocity-api') compile project(':velocity-native') + compile "io.netty:netty-codec:${nettyVersion}" compile "io.netty:netty-codec-http:${nettyVersion}" compile "io.netty:netty-handler:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64" + compile "org.apache.logging.log4j:log4j-api:${log4jVersion}" compile "org.apache.logging.log4j:log4j-core:${log4jVersion}" + + 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 + testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" } artifacts { archives shadowJar -} \ No newline at end of file +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index 7853d1639..6efd6a6c7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -1,12 +1,13 @@ package com.velocitypowered.proxy; +import com.velocitypowered.proxy.console.VelocityConsole; + public class Velocity { - public static void main(String... args) throws InterruptedException { + public static void main(String... args) { final VelocityServer server = VelocityServer.getServer(); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread")); - - Thread.currentThread().join(); + new VelocityConsole(server).start(); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 43c0365ec..672414629 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -56,8 +56,7 @@ public class VelocityServer implements ProxyServer { private final CommandInvoker consoleCommandInvoker = new CommandInvoker() { @Override public void sendMessage(@Nonnull Component component) { - // TODO: TerminalConsoleAppender - logger.info(ComponentSerializers.PLAIN.serialize(component)); + logger.info(ComponentSerializers.LEGACY.serialize(component)); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java new file mode 100644 index 000000000..2ac760e3e --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -0,0 +1,39 @@ +package com.velocitypowered.proxy.console; + +import com.velocitypowered.proxy.VelocityServer; +import net.minecrell.terminalconsole.SimpleTerminalConsole; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; + +public final class VelocityConsole extends SimpleTerminalConsole { + + private final VelocityServer server; + + public VelocityConsole(VelocityServer server) { + this.server = server; + } + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + return super.buildReader(builder + .appName("Velocity") + // TODO: Command completion + ); + } + + @Override + protected boolean isRunning() { + return !this.server.isShutdown(); + } + + @Override + protected void runCommand(String command) { + this.server.getCommandManager().execute(this.server.getConsoleCommandInvoker(), command); + } + + @Override + protected void shutdown() { + this.server.shutdown(); + } + +} diff --git a/proxy/src/main/resources/log4j2.component.properties b/proxy/src/main/resources/log4j2.component.properties new file mode 100644 index 000000000..6ed08f31f --- /dev/null +++ b/proxy/src/main/resources/log4j2.component.properties @@ -0,0 +1 @@ +log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector diff --git a/proxy/src/main/resources/log4j2.xml b/proxy/src/main/resources/log4j2.xml index bddab2847..ddaefacb2 100644 --- a/proxy/src/main/resources/log4j2.xml +++ b/proxy/src/main/resources/log4j2.xml @@ -1,12 +1,12 @@ - - - + + + - + @@ -16,7 +16,7 @@ - + From a88d27af763c1a08a44e40625e36543d9c24289c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 10:37:38 -0400 Subject: [PATCH 21/88] Ignore repeated shutdown requests. --- .../java/com/velocitypowered/proxy/VelocityServer.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 43c0365ec..633db78f2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -35,6 +35,7 @@ import java.nio.file.Paths; import java.security.KeyPair; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; public class VelocityServer implements ProxyServer { private static final Logger logger = LogManager.getLogger(VelocityServer.class); @@ -49,7 +50,7 @@ public class VelocityServer implements ProxyServer { private KeyPair serverKeyPair; private final ServerMap servers = new ServerMap(); private final CommandManager commandManager = new CommandManager(); - private boolean shutdown = false; + private final AtomicBoolean shutdown = new AtomicBoolean(false); private final Map connectionsByUuid = new ConcurrentHashMap<>(); private final Map connectionsByName = new ConcurrentHashMap<>(); @@ -136,15 +137,12 @@ public class VelocityServer implements ProxyServer { } public boolean isShutdown() { - return shutdown; + return shutdown.get(); } public void shutdown() { - Preconditions.checkState(!shutdown, "Server already shut down"); - shutdown = true; - + if (!shutdown.compareAndSet(false, true)) return; logger.info("Shutting down the proxy..."); - this.cm.shutdown(); } From bb601dca4b129c2e94e340a0fe572f0d8c34c8e3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 11:02:35 -0400 Subject: [PATCH 22/88] Add console tab complete, shutdown command, gracefully kick players. --- .../velocitypowered/proxy/VelocityServer.java | 16 ++++++++-- .../proxy/command/CommandManager.java | 32 +++++++++++++++++++ .../proxy/command/ServerCommand.java | 18 +++++++++++ .../proxy/command/ShutdownCommand.java | 20 ++++++++++++ .../proxy/command/VelocityCommand.java | 4 +-- .../proxy/console/VelocityConsole.java | 21 +++++++++--- 6 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index a6c5a5275..3cff9b6bd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -10,6 +10,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.network.ConnectionManager; 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; @@ -21,6 +22,7 @@ import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.ServerMap; import io.netty.bootstrap.Bootstrap; import net.kyori.text.Component; +import net.kyori.text.TextComponent; import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.GsonComponentSerializer; import org.apache.logging.log4j.LogManager; @@ -50,7 +52,8 @@ public class VelocityServer implements ProxyServer { private KeyPair serverKeyPair; private final ServerMap servers = new ServerMap(); private final CommandManager commandManager = new CommandManager(); - private final AtomicBoolean shutdown = new AtomicBoolean(false); + private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); + private boolean shutdown = false; private final Map connectionsByUuid = new ConcurrentHashMap<>(); private final Map connectionsByName = new ConcurrentHashMap<>(); @@ -69,6 +72,7 @@ public class VelocityServer implements ProxyServer { private VelocityServer() { commandManager.registerCommand("velocity", new VelocityCommand()); commandManager.registerCommand("server", new ServerCommand()); + commandManager.registerCommand("shutdown", new ShutdownCommand()); } public static VelocityServer getServer() { @@ -136,13 +140,19 @@ public class VelocityServer implements ProxyServer { } public boolean isShutdown() { - return shutdown.get(); + return shutdown; } public void shutdown() { - if (!shutdown.compareAndSet(false, true)) return; + if (!shutdownInProgress.compareAndSet(false, true)) return; logger.info("Shutting down the proxy..."); + + for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) { + player.close(TextComponent.of("Proxy shutting down.")); + } + this.cm.shutdown(); + shutdown = true; } public NettyHttpClient getHttpClient() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java index 5a3753089..60eeed2ab 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java @@ -1,12 +1,15 @@ package com.velocitypowered.proxy.command; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.CommandExecutor; import com.velocitypowered.api.command.CommandInvoker; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class CommandManager { private final Map executors = new HashMap<>(); @@ -45,4 +48,33 @@ public class CommandManager { throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + invoker, e); } } + + public List offerSuggestions(CommandInvoker invoker, String cmdLine) { + Preconditions.checkNotNull(invoker, "invoker"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); + + String[] split = cmdLine.split(" ", -1); + if (split.length == 0) { + return ImmutableList.of(); + } + + String command = split[0]; + if (split.length == 1) { + return executors.keySet().stream() + .filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length())) + .collect(Collectors.toList()); + } + + String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); + CommandExecutor executor = executors.get(command); + if (executor == null) { + return ImmutableList.of(); + } + + try { + return executor.suggest(invoker, actualArgs); + } catch (Exception e) { + throw new RuntimeException("Unable to invoke suggestions for command " + command + " for " + invoker, e); + } + } } 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 bb66832ca..16a05d021 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -1,5 +1,6 @@ package com.velocitypowered.proxy.command; +import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.CommandExecutor; import com.velocitypowered.api.command.CommandInvoker; import com.velocitypowered.api.proxy.Player; @@ -9,6 +10,7 @@ import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import javax.annotation.Nonnull; +import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -38,4 +40,20 @@ public class ServerCommand implements CommandExecutor { player.sendMessage(TextComponent.of("Available servers: " + serverList, TextColor.YELLOW)); } } + + @Override + public List suggest(@Nonnull CommandInvoker invoker, @Nonnull String[] currentArgs) { + if (currentArgs.length == 0) { + return VelocityServer.getServer().getAllServers().stream() + .map(ServerInfo::getName) + .collect(Collectors.toList()); + } else if (currentArgs.length == 1) { + return VelocityServer.getServer().getAllServers().stream() + .map(ServerInfo::getName) + .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) + .collect(Collectors.toList()); + } else { + return ImmutableList.of(); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java new file mode 100644 index 000000000..302a49248 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java @@ -0,0 +1,20 @@ +package com.velocitypowered.proxy.command; + +import com.velocitypowered.api.command.CommandExecutor; +import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.proxy.VelocityServer; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +import javax.annotation.Nonnull; + +public class ShutdownCommand implements CommandExecutor { + @Override + public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) { + if (invoker != VelocityServer.getServer().getConsoleCommandInvoker()) { + invoker.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED)); + return; + } + VelocityServer.getServer().shutdown(); + } +} 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 20b77e6bc..272fe4777 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -16,7 +16,7 @@ public class VelocityCommand implements CommandExecutor { TextComponent thisIsVelocity = TextComponent.builder() .content("This is ") .append(TextComponent.of("Velocity " + implVersion, TextColor.DARK_AQUA)) - .append(TextComponent.of(", the next generation Minecraft: Java Edition proxy.", TextColor.WHITE)) + .append(TextComponent.of(", the next generation Minecraft: Java Edition proxy.").resetStyle()) .build(); TextComponent velocityInfo = TextComponent.builder() .content("Copyright 2018 Velocity Contributors. Velocity is freely licensed under the terms of the " + @@ -28,7 +28,7 @@ public class VelocityCommand implements CommandExecutor { .color(TextColor.GREEN) .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com")) .build()) - .append(TextComponent.of(" or the ", TextColor.WHITE)) + .append(TextComponent.of(" or the ").resetStyle()) .append(TextComponent.builder("Velocity GitHub") .color(TextColor.GREEN) .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/astei/velocity")) 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 2ac760e3e..835996148 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -1,9 +1,12 @@ package com.velocitypowered.proxy.console; 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.LineReader; -import org.jline.reader.LineReaderBuilder; +import org.jline.reader.*; + +import java.util.List; public final class VelocityConsole extends SimpleTerminalConsole { @@ -16,8 +19,14 @@ public final class VelocityConsole extends SimpleTerminalConsole { @Override protected LineReader buildReader(LineReaderBuilder builder) { return super.buildReader(builder - .appName("Velocity") - // TODO: Command completion + .appName("Velocity") + .completer((reader, parsedLine, list) -> { + List offers = server.getCommandManager().offerSuggestions(server.getConsoleCommandInvoker(), parsedLine.line()); + for (String offer : offers) { + if (offer.isEmpty()) continue; + list.add(new Candidate(offer)); + } + }) ); } @@ -28,7 +37,9 @@ public final class VelocityConsole extends SimpleTerminalConsole { @Override protected void runCommand(String command) { - this.server.getCommandManager().execute(this.server.getConsoleCommandInvoker(), command); + if (!this.server.getCommandManager().execute(this.server.getConsoleCommandInvoker(), command)) { + server.getConsoleCommandInvoker().sendMessage(TextComponent.of("Command not found.", TextColor.RED)); + } } @Override From 54f9de04dc619907f1109a534134a5256f93669e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 7 Aug 2018 11:53:13 -0400 Subject: [PATCH 23/88] Add MC tab complete (incomplete, only 1.12.2 works) --- .../proxy/command/CommandManager.java | 17 +-- .../client/ClientPlaySessionHandler.java | 28 ++++ .../proxy/console/VelocityConsole.java | 11 +- .../proxy/protocol/StateRegistry.java | 6 + .../protocol/packet/TabCompleteRequest.java | 97 +++++++++++++ .../protocol/packet/TabCompleteResponse.java | 128 ++++++++++++++++++ 6 files changed, 273 insertions(+), 14 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java index 60eeed2ab..a9871a08d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java @@ -5,10 +5,7 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.CommandExecutor; import com.velocitypowered.api.command.CommandInvoker; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class CommandManager { @@ -49,30 +46,30 @@ public class CommandManager { } } - public List offerSuggestions(CommandInvoker invoker, String cmdLine) { + public Optional> offerSuggestions(CommandInvoker invoker, String cmdLine) { Preconditions.checkNotNull(invoker, "invoker"); Preconditions.checkNotNull(cmdLine, "cmdLine"); String[] split = cmdLine.split(" ", -1); if (split.length == 0) { - return ImmutableList.of(); + return Optional.empty(); } String command = split[0]; if (split.length == 1) { - return executors.keySet().stream() + return Optional.of(executors.keySet().stream() .filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length())) - .collect(Collectors.toList()); + .collect(Collectors.toList())); } String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); CommandExecutor executor = executors.get(command); if (executor == null) { - return ImmutableList.of(); + return Optional.empty(); } try { - return executor.suggest(invoker, actualArgs); + return Optional.of(executor.suggest(invoker, actualArgs)); } catch (Exception e) { throw new RuntimeException("Unable to invoke suggestions for command " + command + " for " + invoker, e); } 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 cecc1c80f..7aa02bd6a 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 @@ -95,6 +95,34 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return; } + if (packet instanceof TabCompleteRequest) { + TabCompleteRequest req = (TabCompleteRequest) packet; + int lastSpace = req.getCommand().indexOf(' '); + if (!req.isAssumeCommand() && lastSpace != -1) { + String command = req.getCommand().substring(1); + try { + Optional> offers = VelocityServer.getServer().getCommandManager().offerSuggestions(player, command); + if (offers.isPresent()) { + TabCompleteResponse response = new TabCompleteResponse(); + response.setTransactionId(req.getTransactionId()); + response.setStart(lastSpace); + response.setLength(req.getCommand().length() - lastSpace); + for (String s : offers.get()) { + response.getOffers().add(new TabCompleteResponse.Offer(s, null)); + } + player.getConnection().write(response); + return; + } + } catch (Exception e) { + logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e); + TabCompleteResponse response = new TabCompleteResponse(); + response.setTransactionId(req.getTransactionId()); + player.getConnection().write(response); + return; + } + } + } + if (packet instanceof PluginMessage) { handleClientPluginMessage((PluginMessage) packet); return; 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 835996148..4c46764fa 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -7,6 +7,7 @@ import net.minecrell.terminalconsole.SimpleTerminalConsole; import org.jline.reader.*; import java.util.List; +import java.util.Optional; public final class VelocityConsole extends SimpleTerminalConsole { @@ -21,10 +22,12 @@ public final class VelocityConsole extends SimpleTerminalConsole { return super.buildReader(builder .appName("Velocity") .completer((reader, parsedLine, list) -> { - List offers = server.getCommandManager().offerSuggestions(server.getConsoleCommandInvoker(), parsedLine.line()); - for (String offer : offers) { - if (offer.isEmpty()) continue; - list.add(new Candidate(offer)); + Optional> offers = server.getCommandManager().offerSuggestions(server.getConsoleCommandInvoker(), parsedLine.line()); + if (offers.isPresent()) { + for (String offer : offers.get()) { + if (offer.isEmpty()) continue; + list.add(new Candidate(offer)); + } } }) ); 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 80d0d842c..fea896527 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -32,6 +32,9 @@ public enum StateRegistry { }, PLAY { { + SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new, + map(0x01, MINECRAFT_1_12_2), + map(0x05, MINECRAFT_1_13)); SERVERBOUND.register(Chat.class, Chat::new, map(0x01, MINECRAFT_1_8), map(0x02, MINECRAFT_1_9), @@ -65,6 +68,9 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_9), map(0x0F, MINECRAFT_1_12), map(0x0E, MINECRAFT_1_13)); + CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, + map(0x0E, MINECRAFT_1_12), + map(0x10, MINECRAFT_1_13)); CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, map(0x3F, MINECRAFT_1_8), map(0x18, MINECRAFT_1_9), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java new file mode 100644 index 000000000..94d80b66e --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java @@ -0,0 +1,97 @@ +package com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13; + +public class TabCompleteRequest implements MinecraftPacket { + private int transactionId; + private String command; + private boolean assumeCommand; + private boolean hasPosition; + private long position; + + public int getTransactionId() { + return transactionId; + } + + public void setTransactionId(int transactionId) { + this.transactionId = transactionId; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public boolean isAssumeCommand() { + return assumeCommand; + } + + public void setAssumeCommand(boolean assumeCommand) { + this.assumeCommand = assumeCommand; + } + + public boolean isHasPosition() { + return hasPosition; + } + + public void setHasPosition(boolean hasPosition) { + this.hasPosition = hasPosition; + } + + public long getPosition() { + return position; + } + + public void setPosition(long position) { + this.position = position; + } + + @Override + public String toString() { + return "TabCompleteRequest{" + + "transactionId=" + transactionId + + ", command='" + command + '\'' + + ", assumeCommand=" + assumeCommand + + ", hasPosition=" + hasPosition + + ", position=" + position + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (protocolVersion >= MINECRAFT_1_13) { + this.transactionId = ProtocolUtils.readVarInt(buf); + this.command = ProtocolUtils.readString(buf); + } else { + this.command = ProtocolUtils.readString(buf); + this.assumeCommand = buf.readBoolean(); + this.hasPosition = buf.readBoolean(); + if (hasPosition) { + this.position = buf.readLong(); + } + } + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (protocolVersion >= MINECRAFT_1_13) { + ProtocolUtils.writeVarInt(buf, transactionId); + ProtocolUtils.writeString(buf, command); + } else { + ProtocolUtils.writeString(buf, command); + buf.writeBoolean(assumeCommand); + buf.writeBoolean(hasPosition); + if (hasPosition) { + buf.writeLong(position); + } + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java new file mode 100644 index 000000000..df80fb626 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java @@ -0,0 +1,128 @@ +package com.velocitypowered.proxy.protocol.packet; + +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; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.ComponentSerializers; + +import java.util.ArrayList; +import java.util.List; + +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13; + +public class TabCompleteResponse implements MinecraftPacket { + private int transactionId; + private int start; + private int length; + private final List offers = new ArrayList<>(); + + public int getTransactionId() { + return transactionId; + } + + public void setTransactionId(int transactionId) { + this.transactionId = transactionId; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public List getOffers() { + return offers; + } + + @Override + public String toString() { + return "TabCompleteResponse{" + + "transactionId=" + transactionId + + ", start=" + start + + ", length=" + length + + ", offers=" + offers + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (protocolVersion >= MINECRAFT_1_13) { + this.transactionId = ProtocolUtils.readVarInt(buf); + this.start = ProtocolUtils.readVarInt(buf); + this.length = ProtocolUtils.readVarInt(buf); + int offersAvailable = ProtocolUtils.readVarInt(buf); + for (int i = 0; i < offersAvailable; i++) { + String entry = ProtocolUtils.readString(buf); + Component component = buf.readBoolean() ? ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)) : + null; + offers.add(new Offer(entry, component)); + } + } else { + int offersAvailable = ProtocolUtils.readVarInt(buf); + for (int i = 0; i < offersAvailable; i++) { + offers.add(new Offer(ProtocolUtils.readString(buf), null)); + } + } + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (protocolVersion >= MINECRAFT_1_13) { + ProtocolUtils.writeVarInt(buf, transactionId); + ProtocolUtils.writeVarInt(buf, start); + ProtocolUtils.writeVarInt(buf, length); + ProtocolUtils.writeVarInt(buf, offers.size()); + for (Offer offer : offers) { + ProtocolUtils.writeString(buf, offer.entry); + buf.writeBoolean(offer.tooltip != null); + if (offer.tooltip != null) { + ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(offer.tooltip)); + } + } + } else { + ProtocolUtils.writeVarInt(buf, offers.size()); + for (Offer offer : offers) { + ProtocolUtils.writeString(buf, offer.entry); + } + } + } + + public static class Offer { + private final String entry; + private final Component tooltip; + + public Offer(String entry, Component tooltip) { + this.entry = entry; + this.tooltip = tooltip; + } + + public String getEntry() { + return entry; + } + + public Component getTooltip() { + return tooltip; + } + + @Override + public String toString() { + return "Offer{" + + "entry='" + entry + '\'' + + ", tooltip=" + tooltip + + '}'; + } + } +} From a2618233029b0bf381b5f6e937ab91d59951fb7e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 8 Aug 2018 04:44:27 -0400 Subject: [PATCH 24/88] Add favicon support --- .gitignore | 3 +- .../velocitypowered/api/server/Favicon.java | 88 +++++++++++++++++++ .../com/velocitypowered/proxy/Velocity.java | 6 ++ .../velocitypowered/proxy/VelocityServer.java | 3 + .../proxy/config/VelocityConfiguration.java | 25 ++++++ .../client/StatusSessionHandler.java | 2 +- .../proxy/data/ServerPing.java | 7 +- .../protocol/util/FaviconSerializer.java | 18 ++++ 8 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/server/Favicon.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java diff --git a/.gitignore b/.gitignore index 7ae951cf7..0e54c3ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -121,4 +121,5 @@ gradle-app.setting # Other trash logs/ -/velocity.toml \ No newline at end of file +/velocity.toml +server-icon.png \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/server/Favicon.java b/api/src/main/java/com/velocitypowered/api/server/Favicon.java new file mode 100644 index 000000000..ceeaf517c --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/server/Favicon.java @@ -0,0 +1,88 @@ +package com.velocitypowered.api.server; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nonnull; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.Objects; + +/** + * Represents a Minecraft server favicon. A Minecraft server favicon is a 64x64 image that can be displayed to a remote + * client that sends a Server List Ping packet, and is automatically displayed in the Minecraft client. + */ +public final class Favicon { + private final String base64Url; + + /** + * Directly create a favicon using its Base64 URL directly. You are generally better served by the create() series + * of functions. + * @param base64Url the url for use with this favicon + */ + public Favicon(@Nonnull String base64Url) { + this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url"); + } + + /** + * Returns the Base64-encoded URI for this image. + * @return a URL representing this favicon + */ + public String getBase64Url() { + return base64Url; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Favicon favicon = (Favicon) o; + return Objects.equals(base64Url, favicon.base64Url); + } + + @Override + public int hashCode() { + return Objects.hash(base64Url); + } + + @Override + public String toString() { + return "Favicon{" + + "base64Url='" + base64Url + '\'' + + '}'; + } + + /** + * Creates a new {@code Favicon} from the specified {@code image}. + * @param image the image to use for the favicon + * @return the created {@link Favicon} instance + */ + public static Favicon create(@Nonnull BufferedImage image) { + Preconditions.checkNotNull(image, "image"); + Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, "Image does not have" + + " 64x64 dimensions (found %sx%s)", image.getWidth(), image.getHeight()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "PNG", os); + } catch (IOException e) { + throw new AssertionError(e); + } + return new Favicon("data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray())); + } + + /** + * Creates a new {@code Favicon} by reading the image from the specified {@code path}. + * @param path the path to the image to create a favicon for + * @return the created {@link Favicon} instance + */ + public static Favicon create(@Nonnull Path path) throws IOException { + try (InputStream stream = Files.newInputStream(path)) { + return create(ImageIO.read(stream)); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index 6efd6a6c7..e2ee28950 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -3,6 +3,12 @@ package com.velocitypowered.proxy; import com.velocitypowered.proxy.console.VelocityConsole; public class Velocity { + static { + // We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient. + // Force AWT to work with its head chopped off. + System.setProperty("java.awt.headless", "true"); + } + public static void main(String... args) { final VelocityServer server = VelocityServer.getServer(); server.start(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 3cff9b6bd..e548d54c3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -7,6 +7,7 @@ import com.google.gson.GsonBuilder; import com.velocitypowered.api.command.CommandInvoker; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.server.Favicon; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.network.ConnectionManager; import com.velocitypowered.proxy.command.ServerCommand; @@ -17,6 +18,7 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.command.CommandManager; +import com.velocitypowered.proxy.protocol.util.FaviconSerializer; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.ServerMap; @@ -44,6 +46,7 @@ public class VelocityServer implements ProxyServer { private static final VelocityServer INSTANCE = new VelocityServer(); public static final Gson GSON = new GsonBuilder() .registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer()) + .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) .create(); private final ConnectionManager cm = new ConnectionManager(); 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 10f697903..cd5f5536c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -2,6 +2,7 @@ package com.velocitypowered.proxy.config; import com.google.common.collect.ImmutableMap; import com.moandjiezana.toml.Toml; +import com.velocitypowered.api.server.Favicon; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.api.util.LegacyChatColorUtils; import net.kyori.text.Component; @@ -15,6 +16,7 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,6 +38,7 @@ public class VelocityConfiguration { private final int queryPort; private Component motdAsComponent; + private Favicon favicon; private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, IPForwardingMode ipForwardingMode, Map servers, @@ -124,9 +127,22 @@ public class VelocityConfiguration { logger.warn("ALL packets going through the proxy are going to be compressed. This may hurt performance."); } + loadFavicon(); + return valid; } + private void loadFavicon() { + Path faviconPath = Paths.get("server-icon.png"); + if (Files.exists(faviconPath)) { + try { + this.favicon = Favicon.create(faviconPath); + } catch (Exception e) { + logger.info("Unable to load your server-icon.png, continuing without it.", e); + } + } + } + public InetSocketAddress getBind() { return AddressUtil.parseAddress(bind); } @@ -182,6 +198,14 @@ public class VelocityConfiguration { return compressionLevel; } + public Favicon getFavicon() { + return favicon; + } + + public static Logger getLogger() { + return logger; + } + @Override public String toString() { return "VelocityConfiguration{" + @@ -197,6 +221,7 @@ public class VelocityConfiguration { ", queryEnabled=" + queryEnabled + ", queryPort=" + queryPort + ", motdAsComponent=" + motdAsComponent + + ", favicon=" + favicon + '}'; } 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 e75eafebd..7d5f886bd 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 @@ -41,7 +41,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler { new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()), configuration.getMotdComponent(), - null + configuration.getFavicon() ); StatusResponse response = new StatusResponse(); response.setStatus(VelocityServer.GSON.toJson(ping)); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java b/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java index 72494eaf1..a6cb2a746 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java @@ -1,14 +1,15 @@ package com.velocitypowered.proxy.data; +import com.velocitypowered.api.server.Favicon; import net.kyori.text.Component; public class ServerPing { private final Version version; private final Players players; private final Component description; - private final String favicon; + private final Favicon favicon; - public ServerPing(Version version, Players players, Component description, String favicon) { + public ServerPing(Version version, Players players, Component description, Favicon favicon) { this.version = version; this.players = players; this.description = description; @@ -27,7 +28,7 @@ public class ServerPing { return description; } - public String getFavicon() { + public Favicon getFavicon() { return favicon; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java new file mode 100644 index 000000000..7243e8b06 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java @@ -0,0 +1,18 @@ +package com.velocitypowered.proxy.protocol.util; + +import com.google.gson.*; +import com.velocitypowered.api.server.Favicon; + +import java.lang.reflect.Type; + +public class FaviconSerializer implements JsonSerializer, JsonDeserializer { + @Override + public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return new Favicon(json.getAsString()); + } + + @Override + public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getBase64Url()); + } +} From 6bc5413038be94aa0dccb85e0709deff48f0e91c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 8 Aug 2018 05:59:26 -0400 Subject: [PATCH 25/88] Exclude all Gradle build folders --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0e54c3ff3..cd03f954a 100644 --- a/.gitignore +++ b/.gitignore @@ -102,7 +102,7 @@ hs_err_pid* ### Gradle ### .gradle -/build/ +build/ # Ignore Gradle GUI config gradle-app.setting From 84717a11da41fb63c67941aa27bec5f8ba1d3493 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 8 Aug 2018 09:22:47 -0400 Subject: [PATCH 26/88] Update README.md --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 018884003..b7348b485 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,6 @@ page. ## Status -Velocity is far from finished, but most of the essential pieces are in place: -you can switch between two servers running Minecraft 1.8-1.13. More versions -and functionality is planned. - -You should join us on **irc.spi.gt** `#velocity` or send us a pull request. +Velocity is far from finished, but most of the essential pieces you would +expect are in place. Velocity supports Minecraft 1.8-1.13. More functionality +is planned. From 512b1c2403ac2c7da8c0cb453d868e283b7ddc63 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 8 Aug 2018 09:45:27 -0400 Subject: [PATCH 27/88] Remove erroneously added getter --- .../velocitypowered/proxy/config/VelocityConfiguration.java | 4 ---- 1 file changed, 4 deletions(-) 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 cd5f5536c..8f2a5aa6d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -202,10 +202,6 @@ public class VelocityConfiguration { return favicon; } - public static Logger getLogger() { - return logger; - } - @Override public String toString() { return "VelocityConfiguration{" + From db8b7c807c2fc7209d7d5ec3f8002e1edc2f849c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 8 Aug 2018 10:10:11 -0400 Subject: [PATCH 28/88] Add kqueue transport support --- proxy/build.gradle | 1 + .../network/ConnectionManager.java | 98 ++++++++++++------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/proxy/build.gradle b/proxy/build.gradle index ec0a4f7e9..e977e03fc 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -28,6 +28,7 @@ dependencies { compile "io.netty:netty-handler:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64" + compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64" compile "org.apache.logging.log4j:log4j-api:${log4jVersion}" compile "org.apache.logging.log4j:log4j-core:${log4jVersion}" diff --git a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java index ab5853bc3..35497a361 100644 --- a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java @@ -24,6 +24,7 @@ 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.kqueue.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.ServerSocketChannel; @@ -37,6 +38,7 @@ import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; @@ -53,46 +55,25 @@ import static com.velocitypowered.network.Connections.READ_TIMEOUT; public final class ConnectionManager { private static final Logger logger = LogManager.getLogger(ConnectionManager.class); - private static final String DISABLE_EPOLL_PROPERTY = "velocity.connection.disable-epoll"; - private static final boolean DISABLE_EPOLL = Boolean.getBoolean(DISABLE_EPOLL_PROPERTY); private final Set endpoints = new HashSet<>(); - private final Class serverSocketChannelClass; - private final Class socketChannelClass; - private final Class datagramChannelClass; + private final TransportType transportType; private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; public ConnectionManager() { - final boolean epoll = canUseEpoll(); - if (epoll) { - this.serverSocketChannelClass = EpollServerSocketChannel.class; - this.socketChannelClass = EpollSocketChannel.class; - this.datagramChannelClass = EpollDatagramChannel.class; - this.bossGroup = new EpollEventLoopGroup(0, createThreadFactory("Netty Epoll Boss #%d")); - this.workerGroup = new EpollEventLoopGroup(0, createThreadFactory("Netty Epoll Worker #%d")); - } else { - this.serverSocketChannelClass = NioServerSocketChannel.class; - this.socketChannelClass = NioSocketChannel.class; - this.datagramChannelClass = NioDatagramChannel.class; - this.bossGroup = new NioEventLoopGroup(0, createThreadFactory("Netty Nio Boss #%d")); - this.workerGroup = new NioEventLoopGroup(0, createThreadFactory("Netty Nio Worker #%d")); - } - this.logChannelInformation(epoll); + this.transportType = TransportType.bestType(); + this.bossGroup = transportType.createEventLoopGroup(true); + this.workerGroup = transportType.createEventLoopGroup(false); + this.logChannelInformation(); } - private void logChannelInformation(final boolean epoll) { - final StringBuilder sb = new StringBuilder(); - sb.append("Using channel type "); - sb.append(epoll ? "epoll": "nio"); - if(DISABLE_EPOLL) { - sb.append(String.format(" - epoll explicitly disabled using -D%s=true", DISABLE_EPOLL_PROPERTY)); - } - logger.info(sb.toString()); // TODO: move to logger + private void logChannelInformation() { + logger.info("Using channel type {}", transportType); } public void bind(final InetSocketAddress address) { final ServerBootstrap bootstrap = new ServerBootstrap() - .channel(this.serverSocketChannelClass) + .channel(this.transportType.serverSocketChannelClass) .group(this.bossGroup, this.workerGroup) .childHandler(new ChannelInitializer() { @Override @@ -129,7 +110,7 @@ public final class ConnectionManager { public void queryBind(final String hostname, final int port) { Bootstrap bootstrap = new Bootstrap() - .channel(datagramChannelClass) + .channel(transportType.datagramChannelClass) .group(this.workerGroup) .handler(new GS4QueryHandler()) .localAddress(hostname, port); @@ -147,7 +128,7 @@ public final class ConnectionManager { public Bootstrap createWorker() { return new Bootstrap() - .channel(this.socketChannelClass) + .channel(this.transportType.socketChannelClass) .group(this.workerGroup); } @@ -162,14 +143,61 @@ public final class ConnectionManager { } } - private static boolean canUseEpoll() { - return Epoll.isAvailable() && !DISABLE_EPOLL; - } - private static ThreadFactory createThreadFactory(final String nameFormat) { return new ThreadFactoryBuilder() .setNameFormat(nameFormat) .setDaemon(true) .build(); } + + private enum TransportType { + NIO(NioServerSocketChannel.class, NioSocketChannel.class, NioDatagramChannel.class) { + @Override + public EventLoopGroup createEventLoopGroup(boolean boss) { + String name = "Netty NIO " + (boss ? "Boss" : "Worker") + " #%d"; + return new NioEventLoopGroup(0, createThreadFactory(name)); + } + }, + EPOLL(EpollServerSocketChannel.class, EpollSocketChannel.class, EpollDatagramChannel.class) { + @Override + public EventLoopGroup createEventLoopGroup(boolean boss) { + String name = "Netty Epoll " + (boss ? "Boss" : "Worker") + " #%d"; + return new EpollEventLoopGroup(0, createThreadFactory(name)); + } + }, + KQUEUE(KQueueServerSocketChannel.class, KQueueSocketChannel.class, KQueueDatagramChannel.class) { + @Override + public EventLoopGroup createEventLoopGroup(boolean boss) { + String name = "Netty Kqueue " + (boss ? "Boss" : "Worker") + " #%d"; + return new KQueueEventLoopGroup(0, createThreadFactory(name)); + } + }; + + private final Class serverSocketChannelClass; + private final Class socketChannelClass; + private final Class datagramChannelClass; + + TransportType(Class serverSocketChannelClass, Class socketChannelClass, Class datagramChannelClass) { + this.serverSocketChannelClass = serverSocketChannelClass; + this.socketChannelClass = socketChannelClass; + this.datagramChannelClass = datagramChannelClass; + } + + @Override + public String toString() { + return name().toLowerCase(Locale.US); + } + + public abstract EventLoopGroup createEventLoopGroup(boolean boss); + + public static TransportType bestType() { + if (Epoll.isAvailable()) { + return EPOLL; + } else if (KQueue.isAvailable()) { + return KQUEUE; + } else { + return NIO; + } + } + } } From 254508a5cf941def583ee8f3989ffefddb5f463b Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 9 Aug 2018 03:23:27 -0400 Subject: [PATCH 29/88] Add connection attempt rate-limiting. --- .../velocitypowered/proxy/VelocityServer.java | 6 +++ .../client/HandshakeSessionHandler.java | 6 +++ .../client/LoginSessionHandler.java | 5 +++ .../proxy/util/Ratelimiter.java | 41 +++++++++++++++++++ .../proxy/util/RatelimiterTest.java | 30 ++++++++++++++ .../proxy/util/ServerMapTest.java | 35 ++++++++++++++++ 6 files changed, 123 insertions(+) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index e548d54c3..d202b9b13 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -21,6 +21,7 @@ import com.velocitypowered.proxy.command.CommandManager; import com.velocitypowered.proxy.protocol.util.FaviconSerializer; 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; @@ -71,6 +72,7 @@ public class VelocityServer implements ProxyServer { return true; } }; + private final Ratelimiter ipAttemptLimiter = new Ratelimiter(3000); // TODO: Configurable. private VelocityServer() { commandManager.registerCommand("velocity", new VelocityCommand()); @@ -162,6 +164,10 @@ public class VelocityServer implements ProxyServer { return httpClient; } + public Ratelimiter getIpAttemptLimiter() { + return ipAttemptLimiter; + } + public boolean registerConnection(ConnectedPlayer connection) { String lowerName = connection.getUsername().toLowerCase(Locale.US); if (connectionsByName.putIfAbsent(lowerName, connection) != null) { 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 700709867..a603e7c8a 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 @@ -15,6 +15,7 @@ import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; +import java.net.InetAddress; import java.net.InetSocketAddress; public class HandshakeSessionHandler implements MinecraftSessionHandler { @@ -50,6 +51,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { connection.closeWith(Disconnect.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client"))); return; } else { + InetAddress address = ((InetSocketAddress) connection.getChannel().remoteAddress()).getAddress(); + if (!VelocityServer.getServer().getIpAttemptLimiter().attempt(address)) { + connection.closeWith(Disconnect.create(TextComponent.of("You are logging in too fast, try again later."))); + return; + } connection.setSessionHandler(new LoginSessionHandler(connection)); } break; 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 cc28c91fa..d08351d14 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 @@ -91,6 +91,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler { VelocityServer.getServer().getHttpClient() .get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp))) .thenAcceptAsync(profileResponse -> { + if (inbound.isClosed()) { + // The player disconnected after we authenticated them. + return; + } + try { inbound.enableEncryption(decryptedSharedSecret); } catch (GeneralSecurityException e) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java new file mode 100644 index 000000000..2f49c6e6b --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java @@ -0,0 +1,41 @@ +package com.velocitypowered.proxy.util; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Ticker; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.net.InetAddress; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class Ratelimiter { + private final Cache expiringCache; + private final long timeoutNanos; + + public Ratelimiter(long timeoutMs) { + this(timeoutMs, Ticker.systemTicker()); + } + + @VisibleForTesting + Ratelimiter(long timeoutMs, Ticker ticker) { + this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs); + this.expiringCache = CacheBuilder.newBuilder() + .ticker(ticker) + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .expireAfterWrite(timeoutMs, TimeUnit.MILLISECONDS) + .build(); + } + + public boolean attempt(InetAddress address) { + long expectedNewValue = System.nanoTime() + timeoutNanos; + long last; + try { + last = expiringCache.get(address, () -> expectedNewValue); + } catch (ExecutionException e) { + // It should be impossible for this to fail. + throw new AssertionError(e); + } + return expectedNewValue == last; + } +} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java new file mode 100644 index 000000000..7df52cb96 --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java @@ -0,0 +1,30 @@ +package com.velocitypowered.proxy.util; + +import com.google.common.base.Ticker; +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.jupiter.api.Assertions.*; + +class RatelimiterTest { + + @Test + void attempt() { + long base = System.nanoTime(); + AtomicLong extra = new AtomicLong(); + Ticker testTicker = new Ticker() { + @Override + public long read() { + return base + extra.get(); + } + }; + Ratelimiter ratelimiter = new Ratelimiter(1000, testTicker); + assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); + assertFalse(ratelimiter.attempt(InetAddress.getLoopbackAddress())); + extra.addAndGet(TimeUnit.SECONDS.toNanos(2)); + assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java new file mode 100644 index 000000000..30d2c617d --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java @@ -0,0 +1,35 @@ +package com.velocitypowered.proxy.util; + +import com.velocitypowered.api.server.ServerInfo; +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.*; + +class ServerMapTest { + private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565); + + @Test + void respectsCaseInsensitivity() { + ServerMap map = new ServerMap(); + ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); + map.register(info); + + assertEquals(Optional.of(info), map.getServer("TestServer")); + assertEquals(Optional.of(info), map.getServer("testserver")); + assertEquals(Optional.of(info), map.getServer("TESTSERVER")); + } + + @Test + void rejectsRepeatedRegisterAttempts() { + ServerMap map = new ServerMap(); + ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); + map.register(info); + + ServerInfo willReject = new ServerInfo("TESTSERVER", TEST_ADDRESS); + assertThrows(IllegalArgumentException.class, () -> map.register(willReject)); + } +} \ No newline at end of file From 1f0a4a8228d9ba572de9673a67a89c35bdff6ed4 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 9 Aug 2018 05:24:47 -0400 Subject: [PATCH 30/88] Add support for HMACed player forwarding data. This provides a small degree of security but also makes Velocity "secure by default", especially on shared hosts. --- ...ingMode.java => PlayerInfoForwarding.java} | 2 +- .../proxy/config/VelocityConfiguration.java | 52 +++++++++----- .../backend/LoginSessionHandler.java | 68 +++++++++++++------ .../connection/backend/ServerConnection.java | 6 +- proxy/src/main/resources/velocity.toml | 5 +- 5 files changed, 92 insertions(+), 41 deletions(-) rename proxy/src/main/java/com/velocitypowered/proxy/config/{IPForwardingMode.java => PlayerInfoForwarding.java} (69%) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/IPForwardingMode.java b/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java similarity index 69% rename from proxy/src/main/java/com/velocitypowered/proxy/config/IPForwardingMode.java rename to proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java index 87d449c2e..078c239c5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/IPForwardingMode.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java @@ -1,6 +1,6 @@ package com.velocitypowered.proxy.config; -public enum IPForwardingMode { +public enum PlayerInfoForwarding { NONE, LEGACY, MODERN 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 8f2a5aa6d..13c97eff5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -5,6 +5,7 @@ import com.moandjiezana.toml.Toml; import com.velocitypowered.api.server.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.LogManager; @@ -28,7 +29,7 @@ public class VelocityConfiguration { private final String motd; private final int showMaxPlayers; private final boolean onlineMode; - private final IPForwardingMode ipForwardingMode; + private final PlayerInfoForwarding playerInfoForwardingMode; private final Map servers; private final List attemptConnectionOrder; private final int compressionThreshold; @@ -40,21 +41,25 @@ public class VelocityConfiguration { private Component motdAsComponent; private Favicon favicon; + private final byte[] forwardingSecret; + private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, - IPForwardingMode ipForwardingMode, Map servers, + PlayerInfoForwarding playerInfoForwardingMode, Map servers, List attemptConnectionOrder, int compressionThreshold, - int compressionLevel, boolean queryEnabled, int queryPort) { + int compressionLevel, boolean queryEnabled, int queryPort, + byte[] forwardingSecret) { this.bind = bind; this.motd = motd; this.showMaxPlayers = showMaxPlayers; this.onlineMode = onlineMode; - this.ipForwardingMode = ipForwardingMode; + this.playerInfoForwardingMode = playerInfoForwardingMode; this.servers = servers; this.attemptConnectionOrder = attemptConnectionOrder; this.compressionThreshold = compressionThreshold; this.compressionLevel = compressionLevel; this.queryEnabled = queryEnabled; this.queryPort = queryPort; + this.forwardingSecret = forwardingSecret; } public boolean validate() { @@ -76,9 +81,15 @@ public class VelocityConfiguration { logger.info("Proxy is running in offline mode!"); } - switch (ipForwardingMode) { + switch (playerInfoForwardingMode) { case NONE: - logger.info("IP forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); + logger.info("Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); + break; + case MODERN: + if (forwardingSecret.length == 0) { + logger.error("You don't have a forwarding secret set."); + valid = false; + } break; } @@ -178,8 +189,8 @@ public class VelocityConfiguration { return onlineMode; } - public IPForwardingMode getIpForwardingMode() { - return ipForwardingMode; + public PlayerInfoForwarding getPlayerInfoForwardingMode() { + return playerInfoForwardingMode; } public Map getServers() { @@ -202,6 +213,10 @@ public class VelocityConfiguration { return favicon; } + public byte[] getForwardingSecret() { + return forwardingSecret; + } + @Override public String toString() { return "VelocityConfiguration{" + @@ -209,7 +224,7 @@ public class VelocityConfiguration { ", motd='" + motd + '\'' + ", showMaxPlayers=" + showMaxPlayers + ", onlineMode=" + onlineMode + - ", ipForwardingMode=" + ipForwardingMode + + ", playerInfoForwardingMode=" + playerInfoForwardingMode + ", servers=" + servers + ", attemptConnectionOrder=" + attemptConnectionOrder + ", compressionThreshold=" + compressionThreshold + @@ -218,6 +233,7 @@ public class VelocityConfiguration { ", queryPort=" + queryPort + ", motdAsComponent=" + motdAsComponent + ", favicon=" + favicon + + ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) + '}'; } @@ -236,18 +252,22 @@ public class VelocityConfiguration { } } + byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t") + .getBytes(StandardCharsets.UTF_8); + return new VelocityConfiguration( - toml.getString("bind"), - toml.getString("motd"), - toml.getLong("show-max-players").intValue(), - toml.getBoolean("online-mode"), - IPForwardingMode.valueOf(toml.getString("ip-forwarding").toUpperCase()), + toml.getString("bind", "0.0.0.0:25577"), + toml.getString("motd", "&3A Velocity Server"), + toml.getLong("show-max-players", 500L).intValue(), + toml.getBoolean("online-mode", true), + PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()), ImmutableMap.copyOf(servers), toml.getTable("servers").getList("try"), toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(), toml.getTable("advanced").getLong("compression-level", -1L).intValue(), - toml.getTable("query").getBoolean("enabled"), - toml.getTable("query").getLong("port", 25577L).intValue()); + toml.getTable("query").getBoolean("enabled", false), + toml.getTable("query").getLong("port", 25577L).intValue(), + forwardingSecret); } } } 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 e4219dd16..cf3e776b5 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 @@ -2,7 +2,8 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.config.IPForwardingMode; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; @@ -17,6 +18,11 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelPipeline; import net.kyori.text.TextComponent; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -30,7 +36,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public void activated() { - if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN) { + if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN) { forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> { connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), TextComponent.of("Your server did not send the forwarding request in time. Is it set up correctly?")); @@ -44,12 +50,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { throw new IllegalStateException("Backend server is online-mode!"); } else if (packet instanceof LoginPluginMessage) { LoginPluginMessage message = (LoginPluginMessage) packet; - if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN && + VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); + if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && message.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) { LoginPluginResponse response = new LoginPluginResponse(); response.setSuccess(true); response.setId(message.getId()); - response.setData(createForwardingData(connection.getProxyPlayer().getRemoteAddress().getHostString(), + response.setData(createForwardingData(configuration.getForwardingSecret(), + connection.getProxyPlayer().getRemoteAddress().getHostString(), connection.getProxyPlayer().getProfile())); connection.getMinecraftConnection().write(response); cancelForwardingCheck(); @@ -122,23 +130,43 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } } - private static ByteBuf createForwardingData(String address, GameProfile profile) { - ByteBuf buf = Unpooled.buffer(); - ProtocolUtils.writeString(buf, address); - ProtocolUtils.writeUuid(buf, profile.idAsUuid()); - ProtocolUtils.writeString(buf, profile.getName()); - ProtocolUtils.writeVarInt(buf, profile.getProperties().size()); - for (GameProfile.Property property : profile.getProperties()) { - ProtocolUtils.writeString(buf, property.getName()); - ProtocolUtils.writeString(buf, property.getValue()); - String signature = property.getSignature(); - if (signature != null) { - buf.writeBoolean(true); - ProtocolUtils.writeString(buf, signature); - } else { - buf.writeBoolean(false); + static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) { + ByteBuf dataToForward = Unpooled.buffer(); + ByteBuf finalData = Unpooled.buffer(); + try { + ProtocolUtils.writeString(dataToForward, address); + ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid()); + ProtocolUtils.writeString(dataToForward, profile.getName()); + ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size()); + for (GameProfile.Property property : profile.getProperties()) { + ProtocolUtils.writeString(dataToForward, property.getName()); + ProtocolUtils.writeString(dataToForward, property.getValue()); + String signature = property.getSignature(); + if (signature != null) { + dataToForward.writeBoolean(true); + ProtocolUtils.writeString(dataToForward, signature); + } else { + dataToForward.writeBoolean(false); + } } + + SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(key); + mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes()); + byte[] sig = mac.doFinal(); + finalData.writeBytes(sig); + finalData.writeBytes(dataToForward); + return finalData; + } catch (InvalidKeyException e) { + finalData.release(); + throw new RuntimeException("Unable to authenticate data", e); + } catch (NoSuchAlgorithmException e) { + // Should never happen + finalData.release(); + throw new AssertionError(e); + } finally { + dataToForward.release(); } - return buf; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java index 08f58325f..1ee76af1a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; -import com.velocitypowered.proxy.config.IPForwardingMode; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -97,7 +97,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); - if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.LEGACY) { + if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createBungeeForwardingAddress()); } else { handshake.setServerAddress(serverInfo.getAddress().getHostString()); @@ -111,7 +111,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { // Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding. if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 || - VelocityServer.getServer().getConfiguration().getIpForwardingMode() != IPForwardingMode.MODERN) { + VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() != PlayerInfoForwarding.MODERN) { ServerLogin login = new ServerLogin(); login.setUsername(proxyPlayer.getUsername()); minecraftConnection.write(login); diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml index 18aac1e6d..3bdd006e1 100644 --- a/proxy/src/main/resources/velocity.toml +++ b/proxy/src/main/resources/velocity.toml @@ -19,7 +19,10 @@ online-mode = true # servers using Minecraft 1.12 or lower. # - "modern": Forward player IPs and UUIDs as part of the login process using Velocity's native # forwarding. Only applicable for Minecraft 1.13 or higher. -ip-forwarding = "modern" +player-info-forwarding = "modern" + +# If you are using modern IP forwarding, configure an unique secret here. +player-info-forwarding-secret = "5up3r53cr3t" [servers] # Configure your servers here. From 2e1de306fb41bddecb2991fa907610606a835cb3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 9 Aug 2018 10:14:44 -0400 Subject: [PATCH 31/88] Complete tab-complete support back to Minecraft 1.8. --- .../velocitypowered/proxy/protocol/StateRegistry.java | 7 ++++++- .../proxy/protocol/packet/TabCompleteRequest.java | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) 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 fea896527..43f79eb8e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -33,7 +33,10 @@ public enum StateRegistry { PLAY { { SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new, - map(0x01, MINECRAFT_1_12_2), + map(0x14, MINECRAFT_1_8), + map(0x01, MINECRAFT_1_9), + map(0x02, MINECRAFT_1_12), + map(0x01, MINECRAFT_1_12_1), map(0x05, MINECRAFT_1_13)); SERVERBOUND.register(Chat.class, Chat::new, map(0x01, MINECRAFT_1_8), @@ -69,6 +72,8 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_12), map(0x0E, MINECRAFT_1_13)); CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, + map(0x3A, MINECRAFT_1_8), + map(0x0E, MINECRAFT_1_9), map(0x0E, MINECRAFT_1_12), map(0x10, MINECRAFT_1_13)); CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java index 94d80b66e..10c14b400 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java @@ -6,6 +6,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9; public class TabCompleteRequest implements MinecraftPacket { private int transactionId; @@ -72,7 +73,9 @@ public class TabCompleteRequest implements MinecraftPacket { this.command = ProtocolUtils.readString(buf); } else { this.command = ProtocolUtils.readString(buf); - this.assumeCommand = buf.readBoolean(); + if (protocolVersion >= MINECRAFT_1_9) { + this.assumeCommand = buf.readBoolean(); + } this.hasPosition = buf.readBoolean(); if (hasPosition) { this.position = buf.readLong(); @@ -87,7 +90,9 @@ public class TabCompleteRequest implements MinecraftPacket { ProtocolUtils.writeString(buf, command); } else { ProtocolUtils.writeString(buf, command); - buf.writeBoolean(assumeCommand); + if (protocolVersion >= MINECRAFT_1_9) { + buf.writeBoolean(assumeCommand); + } buf.writeBoolean(hasPosition); if (hasPosition) { buf.writeLong(position); From b78ddf7b8510e25388a9cc4231970d660f5247d5 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 9 Aug 2018 10:15:19 -0400 Subject: [PATCH 32/88] Fix scoreboard teams on Minecraft 1.8. --- .../proxy/protocol/packet/ScoreboardTeam.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ScoreboardTeam.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ScoreboardTeam.java index 25a0b6bb3..e6a3d7421 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ScoreboardTeam.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ScoreboardTeam.java @@ -138,7 +138,9 @@ public class ScoreboardTeam implements MinecraftPacket { } this.flags = buf.readByte(); this.nameTagVisibility = ProtocolUtils.readString(buf, 32); - this.collisionRule = ProtocolUtils.readString(buf, 32); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { + this.collisionRule = ProtocolUtils.readString(buf, 32); + } this.color = protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ? buf.readByte() : ProtocolUtils.readVarInt(buf); if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) { @@ -172,7 +174,9 @@ public class ScoreboardTeam implements MinecraftPacket { } buf.writeByte(flags); ProtocolUtils.writeString(buf, nameTagVisibility); - ProtocolUtils.writeString(buf, collisionRule); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { + ProtocolUtils.writeString(buf, collisionRule); + } if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) { ProtocolUtils.writeVarInt(buf, color); ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix); From 58e072ab9492350062cd2ceca6532e2a3536827f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 9 Aug 2018 20:56:12 -0400 Subject: [PATCH 33/88] Bumped to kyoripowered/text 1.12-1.6.2 --- api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build.gradle b/api/build.gradle index 8021a51dd..b4398cb5f 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { compile 'com.google.code.gson:gson:2.8.5' compile "com.google.guava:guava:${guavaVersion}" - compile 'net.kyori:text:1.12-1.6.0-SNAPSHOT' + compile 'net.kyori:text:1.12-1.6.2' compile 'com.moandjiezana.toml:toml4j:0.7.2' testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" From 9af6829688431fa7234fef64634ff38584d5e7d1 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 10 Aug 2018 03:05:59 -0400 Subject: [PATCH 34/88] Fix sending commands to remote server. --- .../proxy/connection/client/ClientPlaySessionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7aa02bd6a..06579a162 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 @@ -82,7 +82,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (msg.startsWith("/")) { try { if (!VelocityServer.getServer().getCommandManager().execute(player, msg.substring(1))) { - player.getConnectedServer().getMinecraftConnection().write(msg); + player.getConnectedServer().getMinecraftConnection().write(chat); } } catch (Exception e) { logger.info("Exception occurred while running command for {}", player.getProfile().getName(), e); From d752edc92d85e16a6ca143dd400936ff681bac15 Mon Sep 17 00:00:00 2001 From: MatrixTunnel Date: Fri, 10 Aug 2018 11:08:55 -0700 Subject: [PATCH 35/88] Add login ratelimit configuration --- .../velocitypowered/proxy/VelocityServer.java | 4 +++- .../proxy/config/VelocityConfiguration.java | 17 +++++++++++++++-- .../proxy/util/Ratelimiter.java | 18 ++++++++++++------ proxy/src/main/resources/velocity.toml | 4 ++++ 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index d202b9b13..aea8c08ba 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -72,7 +72,7 @@ public class VelocityServer implements ProxyServer { return true; } }; - private final Ratelimiter ipAttemptLimiter = new Ratelimiter(3000); // TODO: Configurable. + private Ratelimiter ipAttemptLimiter; private VelocityServer() { commandManager.registerCommand("velocity", new VelocityCommand()); @@ -127,6 +127,8 @@ public class VelocityServer implements ProxyServer { serverKeyPair = EncryptionUtils.createRsaKeyPair(1024); + ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit()); + httpClient = new NettyHttpClient(this); this.cm.bind(configuration.getBind()); 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 13c97eff5..f18dee55c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -34,6 +34,7 @@ public class VelocityConfiguration { private final List attemptConnectionOrder; private final int compressionThreshold; private final int compressionLevel; + private final int loginRatelimit; private final boolean queryEnabled; private final int queryPort; @@ -46,8 +47,8 @@ public class VelocityConfiguration { private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, PlayerInfoForwarding playerInfoForwardingMode, Map servers, List attemptConnectionOrder, int compressionThreshold, - int compressionLevel, boolean queryEnabled, int queryPort, - byte[] forwardingSecret) { + int compressionLevel, int loginRatelimit, boolean queryEnabled, + int queryPort, byte[] forwardingSecret) { this.bind = bind; this.motd = motd; this.showMaxPlayers = showMaxPlayers; @@ -57,6 +58,7 @@ public class VelocityConfiguration { this.attemptConnectionOrder = attemptConnectionOrder; this.compressionThreshold = compressionThreshold; this.compressionLevel = compressionLevel; + this.loginRatelimit = loginRatelimit; this.queryEnabled = queryEnabled; this.queryPort = queryPort; this.forwardingSecret = forwardingSecret; @@ -138,6 +140,11 @@ public class VelocityConfiguration { logger.warn("ALL packets going through the proxy are going to be compressed. This may hurt performance."); } + if (loginRatelimit < 0) { + logger.error("Invalid login ratelimit {}", loginRatelimit); + valid = false; + } + loadFavicon(); return valid; @@ -209,6 +216,10 @@ public class VelocityConfiguration { return compressionLevel; } + public int getLoginRatelimit() { + return loginRatelimit; + } + public Favicon getFavicon() { return favicon; } @@ -229,6 +240,7 @@ public class VelocityConfiguration { ", attemptConnectionOrder=" + attemptConnectionOrder + ", compressionThreshold=" + compressionThreshold + ", compressionLevel=" + compressionLevel + + ", loginRatelimit=" + loginRatelimit + ", queryEnabled=" + queryEnabled + ", queryPort=" + queryPort + ", motdAsComponent=" + motdAsComponent + @@ -265,6 +277,7 @@ public class VelocityConfiguration { toml.getTable("servers").getList("try"), toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(), toml.getTable("advanced").getLong("compression-level", -1L).intValue(), + toml.getTable("advanced").getLong("login-ratelimit", 3000L).intValue(), toml.getTable("query").getBoolean("enabled", false), toml.getTable("query").getLong("port", 25577L).intValue(), forwardingSecret); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java index 2f49c6e6b..6095a56cd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java @@ -19,15 +19,21 @@ public class Ratelimiter { @VisibleForTesting Ratelimiter(long timeoutMs, Ticker ticker) { - this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs); - this.expiringCache = CacheBuilder.newBuilder() - .ticker(ticker) - .concurrencyLevel(Runtime.getRuntime().availableProcessors()) - .expireAfterWrite(timeoutMs, TimeUnit.MILLISECONDS) - .build(); + if (timeoutMs == 0) { + this.timeoutNanos = timeoutMs; + this.expiringCache = null; + } else { + this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs); + this.expiringCache = CacheBuilder.newBuilder() + .ticker(ticker) + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .expireAfterWrite(timeoutMs, TimeUnit.MILLISECONDS) + .build(); + } } public boolean attempt(InetAddress address) { + if (timeoutNanos == 0) return true; long expectedNewValue = System.nanoTime() + timeoutNanos; long last; try { diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml index 3bdd006e1..24a7921bd 100644 --- a/proxy/src/main/resources/velocity.toml +++ b/proxy/src/main/resources/velocity.toml @@ -43,6 +43,10 @@ compression-threshold = 1024 # How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6. compression-level = -1 +# How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000 +# Disable by setting to 0 +login-ratelimit = 3000 + [query] # Whether to enable responding to GameSpy 4 query responses or not enabled = false From 479592c009c1c7982015d310a0a4faaa53aba8c0 Mon Sep 17 00:00:00 2001 From: MatrixTunnel Date: Fri, 10 Aug 2018 11:13:53 -0700 Subject: [PATCH 36/88] Fix invalid compression values not stopping proxy --- .../com/velocitypowered/proxy/config/VelocityConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) 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 13c97eff5..50ed31f27 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -128,12 +128,14 @@ public class VelocityConfiguration { if (compressionLevel < -1 || compressionLevel > 9) { logger.error("Invalid compression level {}", compressionLevel); + valid = false; } else if (compressionLevel == 0) { logger.warn("ALL packets going through the proxy are going to be uncompressed. This will increase bandwidth usage."); } if (compressionThreshold < -1) { logger.error("Invalid compression threshold {}", compressionLevel); + valid = false; } else if (compressionThreshold == 0) { logger.warn("ALL packets going through the proxy are going to be compressed. This may hurt performance."); } From 8998bc7c8f427080cd46f2026901dc034ea55741 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 10 Aug 2018 20:56:22 -0400 Subject: [PATCH 37/88] Presize the maps. TODO: Replace that ugly autoboxed primitive without excessively blowing up the size of the JAR --- .../com/velocitypowered/proxy/protocol/StateRegistry.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 43f79eb8e..79cfc4942 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -167,7 +167,7 @@ public enum StateRegistry { private final ProtocolConstants.Direction direction; private final StateRegistry state; - private final IntObjectMap versions = new IntObjectHashMap<>(); + private final IntObjectMap versions = new IntObjectHashMap<>(16); public PacketRegistry(Direction direction, StateRegistry state) { this.direction = direction; @@ -216,8 +216,8 @@ public enum StateRegistry { public class ProtocolVersion { public final int id; - final IntObjectMap> packetIdToSupplier = new IntObjectHashMap<>(); - final Map, Integer> packetClassToId = new HashMap<>(); + final IntObjectMap> packetIdToSupplier = new IntObjectHashMap<>(16, 0.5f); + final Map, Integer> packetClassToId = new HashMap<>(16, 0.5f); ProtocolVersion(final int id) { this.id = id; From e9959f81fb34c8c73532eff594b60c915c824113 Mon Sep 17 00:00:00 2001 From: kashike Date: Fri, 10 Aug 2018 18:32:27 -0700 Subject: [PATCH 38/88] use fastutil's Object2IntOpenHashMap --- proxy/build.gradle | 43 +++++++++++++++++++ .../proxy/protocol/StateRegistry.java | 9 ++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/proxy/build.gradle b/proxy/build.gradle index e977e03fc..af559e9ec 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -37,10 +37,53 @@ dependencies { runtime 'net.java.dev.jna:jna:4.5.2' // Needed for JLine runtime 'com.lmax:disruptor:3.4.2' // Async loggers + compile 'it.unimi.dsi:fastutil:8.2.1' + testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" } +shadowJar { + exclude 'it/unimi/dsi/fastutil/booleans/**' + exclude 'it/unimi/dsi/fastutil/bytes/**' + exclude 'it/unimi/dsi/fastutil/chars/**' + exclude 'it/unimi/dsi/fastutil/doubles/**' + exclude 'it/unimi/dsi/fastutil/floats/**' + exclude 'it/unimi/dsi/fastutil/ints/*Int2*' + exclude 'it/unimi/dsi/fastutil/ints/IntAVL*' + exclude 'it/unimi/dsi/fastutil/ints/IntArray*' + exclude 'it/unimi/dsi/fastutil/ints/IntBi*' + exclude 'it/unimi/dsi/fastutil/ints/IntList*' + exclude 'it/unimi/dsi/fastutil/ints/IntOpen*' + exclude 'it/unimi/dsi/fastutil/ints/IntRB*' + exclude 'it/unimi/dsi/fastutil/ints/IntSet*' + exclude 'it/unimi/dsi/fastutil/ints/IntSorted*' + exclude 'it/unimi/dsi/fastutil/io/**' + exclude 'it/unimi/dsi/fastutil/longs/**' + exclude 'it/unimi/dsi/fastutil/objects/*ObjectArray*' + exclude 'it/unimi/dsi/fastutil/objects/*ObjectAVL*' + exclude 'it/unimi/dsi/fastutil/objects/*Object*Big*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Boolean*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Byte*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Char*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Double*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Float*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2IntArray*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2IntAVL*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2IntLinked*' + exclude 'it/unimi/dsi/fastutil/objects/*Object*OpenCustom*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2IntRB*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2IntSorted*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Long*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Object*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Reference*' + exclude 'it/unimi/dsi/fastutil/objects/*Object2Short*' + exclude 'it/unimi/dsi/fastutil/objects/*ObjectRB*' + exclude 'it/unimi/dsi/fastutil/objects/*ObjectSorted*' + exclude 'it/unimi/dsi/fastutil/objects/*Reference*' + exclude 'it/unimi/dsi/fastutil/shorts/**' +} + artifacts { archives shadowJar } 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 79cfc4942..dc534f058 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -4,6 +4,8 @@ import com.google.common.primitives.ImmutableIntArray; import com.velocitypowered.proxy.protocol.packet.*; import io.netty.util.collection.IntObjectHashMap; 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.function.Supplier; @@ -217,10 +219,11 @@ public enum StateRegistry { public class ProtocolVersion { public final int id; final IntObjectMap> packetIdToSupplier = new IntObjectHashMap<>(16, 0.5f); - final Map, Integer> packetClassToId = new HashMap<>(16, 0.5f); + final Object2IntMap> packetClassToId = new Object2IntOpenHashMap<>(16, 0.5f); ProtocolVersion(final int id) { this.id = id; + this.packetClassToId.defaultReturnValue(Integer.MIN_VALUE); } public MinecraftPacket createPacket(final int id) { @@ -232,8 +235,8 @@ public enum StateRegistry { } public int getPacketId(final MinecraftPacket packet) { - final Integer id = this.packetClassToId.get(packet.getClass()); - if (id == null) { + final int id = this.packetClassToId.getInt(packet.getClass()); + if (id == Integer.MIN_VALUE) { throw new IllegalArgumentException(String.format( "Unable to find id for packet of type %s in %s protocol %s", packet.getClass().getName(), PacketRegistry.this.direction, this.id From 6e4f90dfae1ba51d723867871881b49c75d1202e Mon Sep 17 00:00:00 2001 From: MatrixTunnel Date: Fri, 10 Aug 2018 18:57:10 -0700 Subject: [PATCH 39/88] Add ratelimit tests --- .../velocitypowered/proxy/util/RatelimiterTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 7df52cb96..b9f9ff204 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java @@ -12,7 +12,14 @@ import static org.junit.jupiter.api.Assertions.*; class RatelimiterTest { @Test - void attempt() { + void attemptZero() { + Ratelimiter noRatelimiter = new Ratelimiter(0); + assertTrue(noRatelimiter.attempt(InetAddress.getLoopbackAddress())); + assertTrue(noRatelimiter.attempt(InetAddress.getLoopbackAddress())); + } + + @Test + void attemptOne() { long base = System.nanoTime(); AtomicLong extra = new AtomicLong(); Ticker testTicker = new Ticker() { @@ -27,4 +34,5 @@ class RatelimiterTest { extra.addAndGet(TimeUnit.SECONDS.toNanos(2)); assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); } -} \ No newline at end of file + +} From 8bf3b99b101d87e1841e0a95d597d99f572bb3b5 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 10 Aug 2018 23:40:26 -0400 Subject: [PATCH 40/88] Make sure the client has time to respond to the player info packet. Apparently, Minecraft 1.13 can take a little too long to respond to Velocity's player info forwarding packet. This especially noticeable in offline mode: by the time the client does respond, Velocity has already completed the login process and tried connecting to the server (it is very quick under offline mode). Noticed by Leymooo. --- .../client/LoginSessionHandler.java | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) 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 d08351d14..594b8ce26 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 @@ -41,39 +41,33 @@ public class LoginSessionHandler implements MinecraftSessionHandler { this.inbound = Preconditions.checkNotNull(inbound, "inbound"); } - @Override - public void activated() { - if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) { - LoginPluginMessage message = new LoginPluginMessage(); - playerInfoId = ThreadLocalRandom.current().nextInt(); - message.setId(playerInfoId); - message.setChannel(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL); - message.setData(Unpooled.EMPTY_BUFFER); - inbound.write(message); - } - } - @Override public void handle(MinecraftPacket packet) { if (packet instanceof LoginPluginResponse) { LoginPluginResponse lpr = (LoginPluginResponse) packet; - if (lpr.getId() == playerInfoId && lpr.isSuccess()) { - // Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening. - inbound.closeWith(Disconnect.create( - TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED) - )); + if (lpr.getId() == playerInfoId) { + if (lpr.isSuccess()) { + // Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening. + inbound.closeWith(Disconnect.create( + TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED) + )); + } else { + // Proceed with the regular login process. + initiateLogin(); + } } } else if (packet instanceof ServerLogin) { this.login = (ServerLogin) packet; - if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { - // Request encryption. - EncryptionRequest request = generateRequest(); - this.verify = Arrays.copyOf(request.getVerifyToken(), 4); - inbound.write(request); + if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) { + LoginPluginMessage message = new LoginPluginMessage(); + playerInfoId = ThreadLocalRandom.current().nextInt(); + message.setId(playerInfoId); + message.setChannel(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL); + message.setData(Unpooled.EMPTY_BUFFER); + inbound.write(message); } else { - // Offline-mode, don't try to request encryption. - handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); + initiateLogin(); } } else if (packet instanceof EncryptionResponse) { try { @@ -119,6 +113,18 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } } + private void initiateLogin() { + if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { + // Request encryption. + EncryptionRequest request = generateRequest(); + this.verify = Arrays.copyOf(request.getVerifyToken(), 4); + inbound.write(request); + } else { + // Offline-mode, don't try to request encryption. + handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); + } + } + private EncryptionRequest generateRequest() { byte[] verify = new byte[4]; ThreadLocalRandom.current().nextBytes(verify); From 95bd152fee4672133d8f81d3ee8304fdc7763820 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 10 Aug 2018 23:40:35 -0400 Subject: [PATCH 41/88] More protocol sanity. --- .../proxy/connection/backend/ServerConnection.java | 7 ++++--- .../proxy/protocol/netty/MinecraftDecoder.java | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java index 1ee76af1a..36b56366c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -93,11 +93,13 @@ public class ServerConnection implements MinecraftConnectionAssociation { } private void startHandshake() { + PlayerInfoForwarding forwardingMode = VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode(); + // Initiate a handshake. Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); - if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { + if (forwardingMode == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createBungeeForwardingAddress()); } else { handshake.setServerAddress(serverInfo.getAddress().getHostString()); @@ -110,8 +112,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { minecraftConnection.setState(StateRegistry.LOGIN); // Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding. - if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 || - VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() != PlayerInfoForwarding.MODERN) { + if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 || forwardingMode != PlayerInfoForwarding.MODERN) { ServerLogin login = new ServerLogin(); login.setUsername(proxyPlayer.getUsername()); minecraftConnection.write(login); 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 f4baba7ac..4e05b3647 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 @@ -40,6 +40,10 @@ public class MinecraftDecoder extends MessageToMessageDecoder { throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction + " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId), e); } + if (msg.isReadable()) { + throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " Direction " + direction + + " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId)); + } out.add(packet); } } From 8068f7272978fb1c74fef4eb4b36d8c63988c029 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Aug 2018 00:22:24 -0400 Subject: [PATCH 42/88] Fix up player info forwarding. --- .../backend/LoginSessionHandler.java | 64 ++++++------------- .../connection/backend/ServerConnection.java | 40 +++--------- .../connection/client/ConnectedPlayer.java | 4 +- .../util/ConnectionRequestResults.java | 15 +++++ 4 files changed, 47 insertions(+), 76 deletions(-) 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 cf3e776b5..24145a4f2 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 @@ -15,7 +15,6 @@ import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelPipeline; import net.kyori.text.TextComponent; import javax.crypto.Mac; @@ -23,27 +22,16 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletableFuture; public class LoginSessionHandler implements MinecraftSessionHandler { private final ServerConnection connection; - private ScheduledFuture forwardingCheckTask; + private boolean informationForwarded; public LoginSessionHandler(ServerConnection connection) { this.connection = connection; } - @Override - public void activated() { - if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN) { - forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> { - connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), - TextComponent.of("Your server did not send the forwarding request in time. Is it set up correctly?")); - }, 1, TimeUnit.SECONDS); - } - } - @Override public void handle(MinecraftPacket packet) { if (packet instanceof EncryptionRequest) { @@ -60,11 +48,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { connection.getProxyPlayer().getRemoteAddress().getHostString(), connection.getProxyPlayer().getProfile())); connection.getMinecraftConnection().write(response); - cancelForwardingCheck(); - - ServerLogin login = new ServerLogin(); - login.setUsername(connection.getProxyPlayer().getUsername()); - connection.getMinecraftConnection().write(login); + informationForwarded = true; } else { // Don't understand LoginPluginResponse response = new LoginPluginResponse(); @@ -75,16 +59,21 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } } else if (packet instanceof Disconnect) { Disconnect disconnect = (Disconnect) packet; - connection.disconnect(); - // Do we have an outstanding notification? If so, fulfill it. doNotify(ConnectionRequestResults.forDisconnect(disconnect)); - - connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect); + connection.disconnect(); } else if (packet instanceof SetCompression) { SetCompression sc = (SetCompression) packet; connection.getMinecraftConnection().setCompressionThreshold(sc.getThreshold()); } else if (packet instanceof ServerLoginSuccess) { + if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && + !informationForwarded) { + doNotify(ConnectionRequestResults.forDisconnect( + TextComponent.of("Your server did not send a forwarding request to the proxy. Is it set up correctly?"))); + connection.disconnect(); + return; + } + // The player has been logged on to the backend server. connection.getMinecraftConnection().setState(StateRegistry.PLAY); ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer(); @@ -96,37 +85,26 @@ public class LoginSessionHandler implements MinecraftSessionHandler { existingConnection.disconnect(); } - // Do we have an outstanding notification? If so, fulfill it. doNotify(ConnectionRequestResults.SUCCESSFUL); - connection.getMinecraftConnection().setSessionHandler(new BackendPlaySessionHandler(connection)); connection.getProxyPlayer().setConnectedServer(connection); } } - @Override - public void deactivated() { - cancelForwardingCheck(); - } - @Override public void exception(Throwable throwable) { - connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable); - } - - private void doNotify(ConnectionRequestBuilder.Result result) { - ChannelPipeline pipeline = connection.getMinecraftConnection().getChannel().pipeline(); - ServerConnection.ConnectionNotifier n = pipeline.get(ServerConnection.ConnectionNotifier.class); - if (n != null) { - n.getResult().complete(result); - pipeline.remove(ServerConnection.ConnectionNotifier.class); + CompletableFuture future = connection.getMinecraftConnection().getChannel() + .attr(ServerConnection.CONNECTION_NOTIFIER).getAndSet(null); + if (future != null) { + future.completeExceptionally(throwable); } } - private void cancelForwardingCheck() { - if (forwardingCheckTask != null) { - forwardingCheckTask.cancel(false); - forwardingCheckTask = null; + private void doNotify(ConnectionRequestBuilder.Result result) { + CompletableFuture future = connection.getMinecraftConnection().getChannel() + .attr(ServerConnection.CONNECTION_NOTIFIER).getAndSet(null); + if (future != null) { + future.complete(result); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java index 36b56366c..98e4cd72d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -18,6 +18,7 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import io.netty.channel.*; import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.util.AttributeKey; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -31,7 +32,8 @@ import static com.velocitypowered.network.Connections.READ_TIMEOUT; import static com.velocitypowered.network.Connections.SERVER_READ_TIMEOUT_SECONDS; public class ServerConnection implements MinecraftConnectionAssociation { - static final String CONNECTION_NOTIFIER = "connection-notifier"; + static final AttributeKey> CONNECTION_NOTIFIER = + AttributeKey.newInstance("connection-notification-result"); private final ServerInfo serverInfo; private final ConnectedPlayer proxyPlayer; @@ -55,9 +57,9 @@ public class ServerConnection implements MinecraftConnectionAssociation { .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)) - .addLast(CONNECTION_NOTIFIER, new ConnectionNotifier(result)); + .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND)); + ch.attr(CONNECTION_NOTIFIER).set(result); MinecraftConnection connection = new MinecraftConnection(ch); connection.setState(StateRegistry.HANDSHAKE); connection.setAssociation(ServerConnection.this); @@ -93,7 +95,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { } private void startHandshake() { - PlayerInfoForwarding forwardingMode = VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode(); + PlayerInfoForwarding forwardingMode = VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode(); // Initiate a handshake. Handshake handshake = new Handshake(); @@ -111,12 +113,9 @@ public class ServerConnection implements MinecraftConnectionAssociation { minecraftConnection.setProtocolVersion(protocolVersion); minecraftConnection.setState(StateRegistry.LOGIN); - // Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding. - if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 || forwardingMode != PlayerInfoForwarding.MODERN) { - ServerLogin login = new ServerLogin(); - login.setUsername(proxyPlayer.getUsername()); - minecraftConnection.write(login); - } + ServerLogin login = new ServerLogin(); + login.setUsername(proxyPlayer.getUsername()); + minecraftConnection.write(login); } public ConnectedPlayer getProxyPlayer() { @@ -140,25 +139,4 @@ public class ServerConnection implements MinecraftConnectionAssociation { public String toString() { return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); } - - static class ConnectionNotifier extends ChannelInboundHandlerAdapter { - private final CompletableFuture result; - - public ConnectionNotifier(CompletableFuture result) { - this.result = result; - } - - public CompletableFuture getResult() { - return result; - } - - public void onComplete() { - result.complete(ConnectionRequestResults.SUCCESSFUL); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - result.completeExceptionally(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 a10681fa8..2cf2b887f 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 @@ -247,7 +247,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { @Override public void fireAndForget() { connect() - .whenComplete((status, throwable) -> { + .whenCompleteAsync((status, throwable) -> { if (throwable != null) { handleConnectionException(info, throwable); return; @@ -267,7 +267,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { handleConnectionException(info, 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/util/ConnectionRequestResults.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java index 8e2d8ec8d..0f6811fde 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java @@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.util; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.proxy.protocol.packet.Disconnect; import net.kyori.text.Component; +import net.kyori.text.TextComponent; import net.kyori.text.serializer.ComponentSerializers; import java.util.Optional; @@ -42,4 +43,18 @@ public class ConnectionRequestResults { } }; } + + public static ConnectionRequestBuilder.Result forDisconnect(TextComponent component) { + return new ConnectionRequestBuilder.Result() { + @Override + public ConnectionRequestBuilder.Status getStatus() { + return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED; + } + + @Override + public Optional getReason() { + return Optional.of(component); + } + }; + } } From 21e72556c964154deee5ed7e5f81690ddaf17a1e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Aug 2018 06:46:40 -0400 Subject: [PATCH 43/88] Correctly handle rapid disconnects. Fixes #31 --- .../proxy/connection/MinecraftConnection.java | 15 +++++++++------ .../backend/BackendPlaySessionHandler.java | 14 ++++++++++++++ .../proxy/connection/client/ConnectedPlayer.java | 1 - 3 files changed, 23 insertions(+), 7 deletions(-) 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 6d9738414..a87c95e40 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -104,18 +104,21 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } public void write(Object msg) { - ensureOpen(); - channel.writeAndFlush(msg, channel.voidPromise()); + if (channel.isActive()) { + channel.writeAndFlush(msg, channel.voidPromise()); + } } public void delayedWrite(Object msg) { - ensureOpen(); - channel.write(msg, channel.voidPromise()); + if (channel.isActive()) { + channel.write(msg, channel.voidPromise()); + } } public void flush() { - ensureOpen(); - channel.flush(); + if (channel.isActive()) { + channel.flush(); + } } public void closeWith(Object msg) { 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 bbf407952..b8975a0b3 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 @@ -18,6 +18,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { + if (!connection.getProxyPlayer().isActive()) { + // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" + // errors. + connection.getMinecraftConnection().close(); + return; + } + ClientPlaySessionHandler playerHandler = (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); if (packet instanceof KeepAlive) { @@ -69,6 +76,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void handleUnknown(ByteBuf buf) { + if (!connection.getProxyPlayer().isActive()) { + // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" + // errors. + connection.getMinecraftConnection().close(); + return; + } + ClientPlaySessionHandler playerHandler = (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); ByteBuf remapped = playerHandler.getIdRemapper().remap(buf, ProtocolConstants.Direction.CLIENTBOUND); 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 2cf2b887f..bf4f05916 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 @@ -132,7 +132,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { String error = ThrowableUtils.briefDescription(throwable); String userMessage; if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { - logger.error("{}: exception occurred in connection to {}", this, info.getName(), throwable); userMessage = "Exception in server " + info.getName(); } else { logger.error("{}: unable to connect to server {}", this, info.getName(), throwable); From 6a3a5a0458dd0a2bffb93f31768a03b090eec5ff Mon Sep 17 00:00:00 2001 From: kashike Date: Sun, 12 Aug 2018 01:33:39 -0700 Subject: [PATCH 44/88] minor shuffle to log messages --- .../java/com/velocitypowered/natives/util/Natives.java | 8 ++++---- .../com/velocitypowered/network/ConnectionManager.java | 5 +++-- .../src/main/java/com/velocitypowered/proxy/Velocity.java | 6 ++++++ .../java/com/velocitypowered/proxy/VelocityServer.java | 5 ----- 4 files changed, 13 insertions(+), 11 deletions(-) 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 2c055bd68..d1f3d2d48 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -40,18 +40,18 @@ public class Natives { public static final NativeCodeLoader compressor = new NativeCodeLoader<>( ImmutableList.of( new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, - copyAndLoadNative("/macosx/velocity-compress.dylib"), "native compression (macOS)", + copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)", NativeVelocityCompressor.FACTORY), new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, - copyAndLoadNative("/linux_x64/velocity-compress.so"), "native compression (Linux amd64)", + copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", NativeVelocityCompressor.FACTORY), - new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java compression", JavaVelocityCompressor.FACTORY) + new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCompressor.FACTORY) ) ); public static final NativeCodeLoader cipher = new NativeCodeLoader<>( ImmutableList.of( - new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java cipher", JavaVelocityCipher.FACTORY) + new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCipher.FACTORY) ) ); } diff --git a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java index 35497a361..d4efdee5e 100644 --- a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java @@ -1,6 +1,7 @@ package com.velocitypowered.network; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -68,7 +69,7 @@ public final class ConnectionManager { } private void logChannelInformation() { - logger.info("Using channel type {}", transportType); + logger.info("Connections will use {} channels, {} compression, {} ciphers", transportType, Natives.compressor.getLoadedVariant(), Natives.cipher.getLoadedVariant()); } public void bind(final InetSocketAddress address) { @@ -168,7 +169,7 @@ public final class ConnectionManager { KQUEUE(KQueueServerSocketChannel.class, KQueueSocketChannel.class, KQueueDatagramChannel.class) { @Override public EventLoopGroup createEventLoopGroup(boolean boss) { - String name = "Netty Kqueue " + (boss ? "Boss" : "Worker") + " #%d"; + String name = "Netty KQueue " + (boss ? "Boss" : "Worker") + " #%d"; return new KQueueEventLoopGroup(0, createThreadFactory(name)); } }; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index e2ee28950..bc20a6059 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -1,8 +1,12 @@ package com.velocitypowered.proxy; import com.velocitypowered.proxy.console.VelocityConsole; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class Velocity { + private static final Logger logger = LogManager.getLogger(Velocity.class); + static { // We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient. // Force AWT to work with its head chopped off. @@ -10,6 +14,8 @@ public class Velocity { } public static void main(String... args) { + logger.info("Booting up Velocity..."); + final VelocityServer server = VelocityServer.getServer(); server.start(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index aea8c08ba..9102ce3fe 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -97,11 +97,6 @@ public class VelocityServer implements ProxyServer { } public void start() { - logger.info("Using {}", Natives.compressor.getLoadedVariant()); - logger.info("Using {}", Natives.cipher.getLoadedVariant()); - - // Create a key pair - logger.info("Booting up Velocity..."); try { Path configPath = Paths.get("velocity.toml"); try { From 6a2b945ed655c611ce995fbd0237e3661954352a Mon Sep 17 00:00:00 2001 From: Leymooo Date: Sun, 12 Aug 2018 02:38:37 +0300 Subject: [PATCH 45/88] Do not handle scoreboards from backend Changes in StateRegistry will allow to us skip packets decode which we don't want handle in BackendPlaySessionHandler for a specific versions Also do not handle respawn packet --- .gitignore | 9 +- .../com/velocitypowered/api/proxy/Player.java | 2 +- .../backend/BackendPlaySessionHandler.java | 11 -- .../client/ClientPlaySessionHandler.java | 104 ------------------ .../proxy/protocol/StateRegistry.java | 70 ++++++------ .../proxy/protocol/PacketRegistryTest.java | 6 +- 6 files changed, 51 insertions(+), 151 deletions(-) diff --git a/.gitignore b/.gitignore index cd03f954a..dc127090a 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,12 @@ modules.xml # BlueJ files *.ctxt +# Eclipse # +**/.classpath +**/.project +**/.settings/ +**/bin/ + # Mobile Tools for Java (J2ME) .mtj.tmp/ @@ -122,4 +128,5 @@ gradle-app.setting # Other trash logs/ /velocity.toml -server-icon.png \ No newline at end of file +server-icon.png +/bin/ \ No newline at end of file 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 89e4df3a6..1e41576a6 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -30,7 +30,7 @@ public interface Player extends CommandInvoker, InboundConnection { * @return an {@link Optional} the server that the player is connected to, which may be empty */ Optional getCurrentServer(); - + /** * Sends a chat message to the player's client. * @param component the chat message to send 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 b8975a0b3..b0784b179 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 @@ -7,7 +7,6 @@ import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; -import io.netty.util.ReferenceCountUtil; public class BackendPlaySessionHandler implements MinecraftSessionHandler { private final ServerConnection connection; @@ -35,10 +34,6 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), original); } else if (packet instanceof JoinGame) { playerHandler.handleBackendJoinGame((JoinGame) packet); - } else if (packet instanceof Respawn) { - // Record the dimension switch, and then forward the packet on. - playerHandler.setCurrentDimension(((Respawn) packet).getDimension()); - connection.getProxyPlayer().getConnection().write(packet); } else if (packet instanceof BossBar) { BossBar bossBar = (BossBar) packet; switch (bossBar.getAction()) { @@ -64,12 +59,6 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { connection.getProxyPlayer().getConnection().write(pm); } else { // Just forward the packet on. We don't have anything to handle at this time. - if (packet instanceof ScoreboardTeam || - packet instanceof ScoreboardObjective || - packet instanceof ScoreboardSetScore || - packet instanceof ScoreboardDisplay) { - playerHandler.handleServerScoreboardPacket(packet); - } connection.getProxyPlayer().getConnection().write(packet); } } 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 06579a162..c1451ef9e 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,12 +1,6 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.command.VelocityCommand; -import com.velocitypowered.api.server.ServerInfo; -import com.velocitypowered.proxy.data.scoreboard.Objective; -import com.velocitypowered.proxy.data.scoreboard.Score; -import com.velocitypowered.proxy.data.scoreboard.Scoreboard; -import com.velocitypowered.proxy.data.scoreboard.Team; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.packet.*; @@ -21,7 +15,6 @@ import net.kyori.text.format.TextColor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; @@ -37,8 +30,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private boolean spawned = false; private final List serverBossBars = new ArrayList<>(); private final Set clientPluginMsgChannels = new HashSet<>(); - private int currentDimension; - private Scoreboard serverScoreboard = new Scoreboard(); private EntityIdRemapper idRemapper; public ClientPlaySessionHandler(ConnectedPlayer player) { @@ -161,7 +152,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (!spawned) { // nothing special to do here spawned = true; - currentDimension = joinGame.getDimension(); player.getConnection().delayedWrite(joinGame); idRemapper = EntityIdRemapper.getMapper(joinGame.getEntityId(), player.getConnection().getProtocolVersion()); } else { @@ -182,13 +172,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { int tempDim = joinGame.getDimension() == 0 ? -1 : 0; player.getConnection().delayedWrite(new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); player.getConnection().delayedWrite(new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); - currentDimension = joinGame.getDimension(); - } - - // Resend client settings packet to remote server if we have it, this preserves client settings across - // transitions. - if (player.getClientSettings() != null) { - player.getConnectedServer().getMinecraftConnection().delayedWrite(player.getClientSettings()); } // Remove old boss bars. @@ -200,9 +183,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } serverBossBars.clear(); - // Remove scoreboard junk. - clearServerScoreboard(); - // Tell the server about this client's plugin messages. Velocity will forward them on to the client. if (!clientPluginMsgChannels.isEmpty()) { String channel = player.getConnection().getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13 ? @@ -216,10 +196,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getConnectedServer().getMinecraftConnection().flush(); } - public void setCurrentDimension(int currentDimension) { - this.currentDimension = currentDimension; - } - public List getServerBossBars() { return serverBossBars; } @@ -261,86 +237,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getConnectedServer().getMinecraftConnection().write(packet); } - public void handleServerScoreboardPacket(MinecraftPacket packet) { - if (packet instanceof ScoreboardDisplay) { - ScoreboardDisplay sd = (ScoreboardDisplay) packet; - serverScoreboard.setPosition(sd.getPosition()); - serverScoreboard.setDisplayName(sd.getDisplayName()); - } - - if (packet instanceof ScoreboardObjective) { - ScoreboardObjective so = (ScoreboardObjective) packet; - switch (so.getMode()) { - case ScoreboardObjective.ADD: - Objective o = new Objective(so.getId()); - o.setDisplayName(so.getDisplayName()); - o.setType(so.getType()); - serverScoreboard.getObjectives().put(so.getId(), o); - break; - case ScoreboardObjective.REMOVE: - serverScoreboard.getObjectives().remove(so.getId()); - break; - } - } - - if (packet instanceof ScoreboardSetScore) { - ScoreboardSetScore sss = (ScoreboardSetScore) packet; - Objective objective = serverScoreboard.getObjectives().get(sss.getObjective()); - if (objective == null) { - return; - } - switch (sss.getAction()) { - case ScoreboardSetScore.CHANGE: - Score score = new Score(sss.getEntity(), sss.getValue()); - objective.getScores().put(sss.getEntity(), score); - break; - case ScoreboardSetScore.REMOVE: - objective.getScores().remove(sss.getEntity()); - break; - } - } - - if (packet instanceof ScoreboardTeam) { - ScoreboardTeam st = (ScoreboardTeam) packet; - switch (st.getMode()) { - case ScoreboardTeam.ADD: - // TODO: Preserve other team information? We might not need to... - Team team = new Team(st.getId()); - serverScoreboard.getTeams().put(st.getId(), team); - break; - case ScoreboardTeam.REMOVE: - serverScoreboard.getTeams().remove(st.getId()); - break; - } - } - } - - private void clearServerScoreboard() { - for (Objective objective : serverScoreboard.getObjectives().values()) { - for (Score score : objective.getScores().values()) { - ScoreboardSetScore sss = new ScoreboardSetScore(); - sss.setObjective(objective.getId()); - sss.setAction(ScoreboardSetScore.REMOVE); - sss.setEntity(score.getTarget()); - player.getConnection().delayedWrite(sss); - } - - ScoreboardObjective so = new ScoreboardObjective(); - so.setId(objective.getId()); - so.setMode(ScoreboardObjective.REMOVE); - player.getConnection().delayedWrite(so); - } - - for (Team team : serverScoreboard.getTeams().values()) { - ScoreboardTeam st = new ScoreboardTeam(); - st.setId(team.getId()); - st.setMode(ScoreboardTeam.REMOVE); - player.getConnection().delayedWrite(st); - } - - serverScoreboard = new Scoreboard(); - } - public Set getClientPluginMsgChannels() { return clientPluginMsgChannels; } 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 dc534f058..1ee67f806 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -99,35 +99,35 @@ public enum StateRegistry { map(0x23, MINECRAFT_1_12), map(0x25, MINECRAFT_1_13)); CLIENTBOUND.register(Respawn.class, Respawn::new, - map(0x07, MINECRAFT_1_8), - map(0x33, MINECRAFT_1_9), - map(0x34, MINECRAFT_1_12), - map(0x35, MINECRAFT_1_12_2), - map(0x38, MINECRAFT_1_13)); + map(0x07, MINECRAFT_1_8, false), + map(0x33, MINECRAFT_1_9, false), + map(0x34, MINECRAFT_1_12, false), + map(0x35, MINECRAFT_1_12_2, false), + map(0x38, MINECRAFT_1_13, false)); CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new, - map(0x3D, MINECRAFT_1_8), - map(0x38, MINECRAFT_1_9), - map(0x3A, MINECRAFT_1_12), - map(0x3B, MINECRAFT_1_12_1), - map(0x3E, MINECRAFT_1_13)); + map(0x3D, MINECRAFT_1_8, false), + map(0x38, MINECRAFT_1_9, false), + map(0x3A, MINECRAFT_1_12, false), + map(0x3B, MINECRAFT_1_12_1, false), + map(0x3E, MINECRAFT_1_13, false)); CLIENTBOUND.register(ScoreboardObjective.class, ScoreboardObjective::new, - map(0x3B, MINECRAFT_1_8), - map(0x3F, MINECRAFT_1_9), - map(0x41, MINECRAFT_1_12), - map(0x42, MINECRAFT_1_12_1), - map(0x45, MINECRAFT_1_13)); + map(0x3B, MINECRAFT_1_8, false), + map(0x3F, MINECRAFT_1_9, false), + map(0x41, MINECRAFT_1_12, false), + map(0x42, MINECRAFT_1_12_1, false), + map(0x45, MINECRAFT_1_13, false)); CLIENTBOUND.register(ScoreboardTeam.class, ScoreboardTeam::new, - map(0x3E, MINECRAFT_1_8), - map(0x41, MINECRAFT_1_9), - map(0x43, MINECRAFT_1_12), - map(0x44, MINECRAFT_1_12_1), - map(0x47, MINECRAFT_1_13)); + map(0x3E, MINECRAFT_1_8, false), + map(0x41, MINECRAFT_1_9, false), + map(0x43, MINECRAFT_1_12, false), + map(0x44, MINECRAFT_1_12_1, false), + map(0x47, MINECRAFT_1_13, false)); CLIENTBOUND.register(ScoreboardSetScore.class, ScoreboardSetScore::new, - map(0x3C, MINECRAFT_1_8), - map(0x42, MINECRAFT_1_9), - map(0x44, MINECRAFT_1_12), - map(0x45, MINECRAFT_1_12_1), - map(0x48, MINECRAFT_1_13)); + map(0x3C, MINECRAFT_1_8, false), + map(0x42, MINECRAFT_1_9, false), + map(0x44, MINECRAFT_1_12, false), + map(0x45, MINECRAFT_1_12_1, false), + map(0x48, MINECRAFT_1_13, false)); } }, LOGIN { @@ -188,6 +188,7 @@ public enum StateRegistry { return result; } + public

void register(Class

clazz, Supplier

packetSupplier, PacketMapping... mappings) { if (mappings.length == 0) { throw new IllegalArgumentException("At least one mapping must be provided."); @@ -198,8 +199,9 @@ public enum StateRegistry { if (version == null) { throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion); } - - version.packetIdToSupplier.put(mapping.id, packetSupplier); + if (mapping.needPacketDecode) { + version.packetIdToSupplier.put(mapping.id, packetSupplier); + } version.packetClassToId.put(clazz, mapping.id); ImmutableIntArray linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion); @@ -210,7 +212,7 @@ public enum StateRegistry { for (PacketMapping m : mappings) { if (linkedVersion == m.protocolVersion) continue links; } - register(clazz, packetSupplier, map(mapping.id, linkedVersion)); + register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.needPacketDecode)); } } } @@ -258,10 +260,12 @@ public enum StateRegistry { public static class PacketMapping { private final int id; private final int protocolVersion; - - public PacketMapping(int id, int protocolVersion) { + private final boolean needPacketDecode; + + public PacketMapping(int id, int protocolVersion, boolean needPacketDecode) { this.id = id; this.protocolVersion = protocolVersion; + this.needPacketDecode = needPacketDecode; } @Override @@ -287,8 +291,12 @@ public enum StateRegistry { } } + private static PacketMapping map(int id, int version, boolean handleFromBackend) { + return new PacketMapping(id, version, handleFromBackend); + } + private static PacketMapping map(int id, int version) { - return new PacketMapping(id, version); + return map(id, version, true); } private static PacketMapping[] genericMappings(int id) { 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 187679f6c..b4541786a 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.*; class PacketRegistryTest { private StateRegistry.PacketRegistry setupRegistry() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE); - registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12)); + registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, true)); return registry; } @@ -44,8 +44,8 @@ class PacketRegistryTest { @Test void registrySuppliesCorrectPacketsByProtocol() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE); - registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12), - new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1)); + registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, true), + new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, true)); assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12).createPacket(0x00).getClass()); assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_1).createPacket(0x01).getClass()); assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_2).createPacket(0x01).getClass()); From 671df77c1f5f2328a7359b9ef2f4254287c2e184 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 12 Aug 2018 08:06:50 -0400 Subject: [PATCH 46/88] Forward the keep-alive packet directly onto the client. This improves anti-cheat compatibility. --- .../backend/BackendPlaySessionHandler.java | 5 +-- .../client/ClientPlaySessionHandler.java | 32 +++++-------------- 2 files changed, 11 insertions(+), 26 deletions(-) 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 b0784b179..bd6ad4d77 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 @@ -27,8 +27,9 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { ClientPlaySessionHandler playerHandler = (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); if (packet instanceof KeepAlive) { - // Forward onto the server - connection.getMinecraftConnection().write(packet); + // Forward onto the player + playerHandler.setLastPing(((KeepAlive) packet).getRandomId()); + connection.getProxyPlayer().getConnection().write(packet); } else if (packet instanceof Disconnect) { Disconnect original = (Disconnect) packet; connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), original); 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 c1451ef9e..549c8fb46 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 @@ -25,7 +25,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private static final int MAX_PLUGIN_CHANNELS = 128; private final ConnectedPlayer player; - private ScheduledFuture pingTask; private long lastPing = -1; private boolean spawned = false; private final List serverBossBars = new ArrayList<>(); @@ -36,30 +35,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { this.player = player; } - @Override - public void activated() { - EventLoop loop = player.getConnection().getChannel().eventLoop(); - pingTask = loop.scheduleAtFixedRate(this::ping, 5, 15, TimeUnit.SECONDS); - } - - private void ping() { - long randomId = ThreadLocalRandom.current().nextInt(); - lastPing = randomId; - KeepAlive keepAlive = new KeepAlive(); - keepAlive.setRandomId(randomId); - player.getConnection().write(keepAlive); - } - @Override public void handle(MinecraftPacket packet) { if (packet instanceof KeepAlive) { KeepAlive keepAlive = (KeepAlive) packet; if (keepAlive.getRandomId() != lastPing) { - throw new IllegalStateException("Client sent invalid keepAlive; expected " + lastPing + ", got " + keepAlive.getRandomId()); + // The last keep alive we got was probably from a different server. Let's ignore it, and hope the next + // ping is alright. + return; } - - // Do not forward the packet to the player's server, because we handle pings for all servers already. - return; } if (packet instanceof ClientSettings) { @@ -132,11 +116,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public void disconnected() { player.teardown(); - - if (pingTask != null && !pingTask.isCancelled()) { - pingTask.cancel(false); - pingTask = null; - } } @Override @@ -149,6 +128,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } public void handleBackendJoinGame(JoinGame joinGame) { + lastPing = Long.MIN_VALUE; // reset last ping if (!spawned) { // nothing special to do here spawned = true; @@ -244,4 +224,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { public EntityIdRemapper getIdRemapper() { return idRemapper; } + + public void setLastPing(long lastPing) { + this.lastPing = lastPing; + } } From 37093d7385378e4bc27951c8da2099508126377f Mon Sep 17 00:00:00 2001 From: kashike Date: Sun, 12 Aug 2018 14:19:02 -0700 Subject: [PATCH 47/88] bump text to 1.12-1.6.4 --- api/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/build.gradle b/api/build.gradle index b4398cb5f..4f14afb33 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { compile 'com.google.code.gson:gson:2.8.5' compile "com.google.guava:guava:${guavaVersion}" - compile 'net.kyori:text:1.12-1.6.2' + compile 'net.kyori:text:1.12-1.6.4' compile 'com.moandjiezana.toml:toml4j:0.7.2' testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" @@ -26,4 +26,4 @@ artifacts { archives javadocJar archives shadowJar archives sourcesJar -} \ No newline at end of file +} From dbe207d0c250b9a2dd856459b3b637e784c37e7a Mon Sep 17 00:00:00 2001 From: MatrixTunnel Date: Mon, 13 Aug 2018 00:44:55 -0700 Subject: [PATCH 48/88] Add startup time to log messages --- .../main/java/com/velocitypowered/proxy/Velocity.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index bc20a6059..fb79d0a80 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -4,9 +4,15 @@ import com.velocitypowered.proxy.console.VelocityConsole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + public class Velocity { private static final Logger logger = LogManager.getLogger(Velocity.class); + private static long startTime; + static { // We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient. // Force AWT to work with its head chopped off. @@ -14,12 +20,15 @@ public class Velocity { } public static void main(String... args) { + startTime = System.currentTimeMillis(); logger.info("Booting up Velocity..."); final VelocityServer server = VelocityServer.getServer(); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread")); + + logger.info("Done ({}s)!", new SimpleDateFormat("ss.S", Locale.getDefault()).format(new Date(System.currentTimeMillis() - startTime))); new VelocityConsole(server).start(); } } From 718f615d3faf42c466ebc2d4390efab38851c965 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 13 Aug 2018 07:23:49 -0400 Subject: [PATCH 49/88] Simplify and improve the startup timer. --- .../src/main/java/com/velocitypowered/proxy/Velocity.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index fb79d0a80..8ab722a2e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -4,9 +4,7 @@ import com.velocitypowered.proxy.console.VelocityConsole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; +import java.text.DecimalFormat; public class Velocity { private static final Logger logger = LogManager.getLogger(Velocity.class); @@ -28,7 +26,8 @@ public class Velocity { Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread")); - logger.info("Done ({}s)!", new SimpleDateFormat("ss.S", Locale.getDefault()).format(new Date(System.currentTimeMillis() - startTime))); + double bootTime = (System.currentTimeMillis() - startTime) / 1000d; + logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime)); new VelocityConsole(server).start(); } } From 9469065b3c66324108083b987cf3ba27d0c2853a Mon Sep 17 00:00:00 2001 From: Leymooo Date: Wed, 15 Aug 2018 18:43:25 +0300 Subject: [PATCH 50/88] Fix PacketMapping toString/equals/hashCode, do not handle Chat and TabCompleteResponse packets from backend --- .../backend/BackendPlaySessionHandler.java | 3 +- .../proxy/protocol/StateRegistry.java | 139 +++++++++--------- 2 files changed, 71 insertions(+), 71 deletions(-) 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 bd6ad4d77..fadabf151 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 @@ -16,7 +16,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } @Override - public void handle(MinecraftPacket packet) { + public void handle(MinecraftPacket packet) { + //Not handleable packets: Chat, TabCompleteResponse, Respawn, Scoreboard* if (!connection.getProxyPlayer().isActive()) { // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // errors. 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 1ee67f806..7dc47d0d4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -13,6 +13,7 @@ import java.util.function.Supplier; import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; public enum StateRegistry { + HANDSHAKE { { SERVERBOUND.register(Handshake.class, Handshake::new, @@ -35,69 +36,69 @@ public enum StateRegistry { PLAY { { SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new, - map(0x14, MINECRAFT_1_8), - map(0x01, MINECRAFT_1_9), - map(0x02, MINECRAFT_1_12), - map(0x01, MINECRAFT_1_12_1), - map(0x05, MINECRAFT_1_13)); + map(0x14, MINECRAFT_1_8, true), + map(0x01, MINECRAFT_1_9, true), + map(0x02, MINECRAFT_1_12, true), + map(0x01, MINECRAFT_1_12_1, true), + map(0x05, MINECRAFT_1_13, true)); SERVERBOUND.register(Chat.class, Chat::new, - map(0x01, MINECRAFT_1_8), - map(0x02, MINECRAFT_1_9), - map(0x03, MINECRAFT_1_12), - map(0x02, MINECRAFT_1_12_2), - map(0x02, MINECRAFT_1_13)); + map(0x01, MINECRAFT_1_8, true), + map(0x02, MINECRAFT_1_9, true), + map(0x03, MINECRAFT_1_12, true), + map(0x02, MINECRAFT_1_12_2, true), + map(0x02, MINECRAFT_1_13, true)); SERVERBOUND.register(ClientSettings.class, ClientSettings::new, - map(0x15, MINECRAFT_1_8), - map(0x04, MINECRAFT_1_9), - map(0x05, MINECRAFT_1_12), - map(0x04, MINECRAFT_1_12_1), - map(0x04, MINECRAFT_1_13)); + map(0x15, MINECRAFT_1_8, true), + map(0x04, MINECRAFT_1_9, true), + map(0x05, MINECRAFT_1_12, true), + map(0x04, MINECRAFT_1_12_1, true), + map(0x04, MINECRAFT_1_13, true)); SERVERBOUND.register(PluginMessage.class, PluginMessage::new, - map(0x17, MINECRAFT_1_8), - map(0x09, MINECRAFT_1_9), - map(0x0A, MINECRAFT_1_12), - map(0x09, MINECRAFT_1_12_1), - map(0x0A, MINECRAFT_1_13)); + map(0x17, MINECRAFT_1_8, true), + map(0x09, MINECRAFT_1_9, true), + map(0x0A, MINECRAFT_1_12, true), + map(0x09, MINECRAFT_1_12_1, true), + map(0x0A, MINECRAFT_1_13, true)); SERVERBOUND.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8), - map(0x0B, MINECRAFT_1_9), - map(0x0C, MINECRAFT_1_12), - map(0x0B, MINECRAFT_1_12_1), - map(0x0E, MINECRAFT_1_13)); + map(0x00, MINECRAFT_1_8, true), + map(0x0B, MINECRAFT_1_9, true), + map(0x0C, MINECRAFT_1_12, true), + map(0x0B, MINECRAFT_1_12_1, true), + map(0x0E, MINECRAFT_1_13, true)); CLIENTBOUND.register(BossBar.class, BossBar::new, - map(0x0C, MINECRAFT_1_9), - map(0x0C, MINECRAFT_1_12)); + map(0x0C, MINECRAFT_1_9, true), + map(0x0C, MINECRAFT_1_12, true)); CLIENTBOUND.register(Chat.class, Chat::new, - map(0x02, MINECRAFT_1_8), - map(0x0F, MINECRAFT_1_9), - map(0x0F, MINECRAFT_1_12), - map(0x0E, MINECRAFT_1_13)); + map(0x02, MINECRAFT_1_8, false), + map(0x0F, MINECRAFT_1_9, false), + map(0x0F, MINECRAFT_1_12, false), + map(0x0E, MINECRAFT_1_13, false)); CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, - map(0x3A, MINECRAFT_1_8), - map(0x0E, MINECRAFT_1_9), - map(0x0E, MINECRAFT_1_12), - map(0x10, MINECRAFT_1_13)); + map(0x3A, MINECRAFT_1_8, false), + map(0x0E, MINECRAFT_1_9, false), + map(0x0E, MINECRAFT_1_12, false), + map(0x10, MINECRAFT_1_13, false)); CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, - map(0x3F, MINECRAFT_1_8), - map(0x18, MINECRAFT_1_9), - map(0x18, MINECRAFT_1_12), - map(0x19, MINECRAFT_1_13)); + map(0x3F, MINECRAFT_1_8, true), + map(0x18, MINECRAFT_1_9, true), + map(0x18, MINECRAFT_1_12, true), + map(0x19, MINECRAFT_1_13, true)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, - map(0x40, MINECRAFT_1_8), - map(0x1A, MINECRAFT_1_9), - map(0x1A, MINECRAFT_1_12), - map(0x1B, MINECRAFT_1_13)); + map(0x40, MINECRAFT_1_8, true), + map(0x1A, MINECRAFT_1_9, true), + map(0x1A, MINECRAFT_1_12, true), + map(0x1B, MINECRAFT_1_13, true)); CLIENTBOUND.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8), - map(0x1F, MINECRAFT_1_9), - map(0x1F, MINECRAFT_1_12), - map(0x21, MINECRAFT_1_13)); + map(0x00, MINECRAFT_1_8, true), + map(0x1F, MINECRAFT_1_9, true), + map(0x1F, MINECRAFT_1_12, true), + map(0x21, MINECRAFT_1_13, true)); CLIENTBOUND.register(JoinGame.class, JoinGame::new, - map(0x01, MINECRAFT_1_8), - map(0x23, MINECRAFT_1_9), - map(0x23, MINECRAFT_1_12), - map(0x25, MINECRAFT_1_13)); + map(0x01, MINECRAFT_1_8, true), + map(0x23, MINECRAFT_1_9, true), + map(0x23, MINECRAFT_1_12, true), + map(0x25, MINECRAFT_1_13, true)); CLIENTBOUND.register(Respawn.class, Respawn::new, map(0x07, MINECRAFT_1_8, false), map(0x33, MINECRAFT_1_9, false), @@ -137,7 +138,7 @@ public enum StateRegistry { SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, genericMappings(0x01)); SERVERBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new, - map(0x02, MINECRAFT_1_13)); + map(0x02, MINECRAFT_1_13, true)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, genericMappings(0x00)); @@ -148,7 +149,7 @@ public enum StateRegistry { CLIENTBOUND.register(SetCompression.class, SetCompression::new, genericMappings(0x03)); CLIENTBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new, - map(0x04, MINECRAFT_1_13)); + map(0x04, MINECRAFT_1_13, true)); } }; @@ -199,7 +200,7 @@ public enum StateRegistry { if (version == null) { throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion); } - if (mapping.needPacketDecode) { + if (mapping.packetDecoding) { version.packetIdToSupplier.put(mapping.id, packetSupplier); } version.packetClassToId.put(clazz, mapping.id); @@ -212,7 +213,7 @@ public enum StateRegistry { for (PacketMapping m : mappings) { if (linkedVersion == m.protocolVersion) continue links; } - register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.needPacketDecode)); + register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.packetDecoding)); } } } @@ -260,12 +261,12 @@ public enum StateRegistry { public static class PacketMapping { private final int id; private final int protocolVersion; - private final boolean needPacketDecode; + private final boolean packetDecoding; - public PacketMapping(int id, int protocolVersion, boolean needPacketDecode) { + public PacketMapping(int id, int protocolVersion, boolean packetDecoding) { this.id = id; this.protocolVersion = protocolVersion; - this.needPacketDecode = needPacketDecode; + this.packetDecoding = packetDecoding; } @Override @@ -273,6 +274,7 @@ public enum StateRegistry { return "PacketMapping{" + "id=" + id + ", protocolVersion=" + protocolVersion + + ", packetDecoding=" + packetDecoding + '}'; } @@ -282,29 +284,26 @@ public enum StateRegistry { if (o == null || getClass() != o.getClass()) return false; PacketMapping that = (PacketMapping) o; return id == that.id && - protocolVersion == that.protocolVersion; + protocolVersion == that.protocolVersion && + packetDecoding == that.packetDecoding; } @Override public int hashCode() { - return Objects.hash(id, protocolVersion); + return Objects.hash(id, protocolVersion, packetDecoding); } } - private static PacketMapping map(int id, int version, boolean handleFromBackend) { - return new PacketMapping(id, version, handleFromBackend); + private static PacketMapping map(int id, int version, boolean packetDecoding) { + return new PacketMapping(id, version, packetDecoding); } - private static PacketMapping map(int id, int version) { - return map(id, version, true); - } - private static PacketMapping[] genericMappings(int id) { return new PacketMapping[]{ - map(id, MINECRAFT_1_8), - map(id, MINECRAFT_1_9), - map(id, MINECRAFT_1_12), - map(id, MINECRAFT_1_13) + map(id, MINECRAFT_1_8, true), + map(id, MINECRAFT_1_9, true), + map(id, MINECRAFT_1_12, true), + map(id, MINECRAFT_1_13, true) }; } } From 381b2033d5d79331123e68e65c043f0ceeda4808 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Fri, 17 Aug 2018 02:02:22 +0300 Subject: [PATCH 51/88] Rename packetDecoding field to onlyEncode --- .../proxy/protocol/StateRegistry.java | 189 +++++++++--------- .../proxy/protocol/PacketRegistryTest.java | 6 +- 2 files changed, 101 insertions(+), 94 deletions(-) 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 7dc47d0d4..6d1d05a12 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -36,99 +36,99 @@ public enum StateRegistry { PLAY { { SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new, - map(0x14, MINECRAFT_1_8, true), - map(0x01, MINECRAFT_1_9, true), - map(0x02, MINECRAFT_1_12, true), - map(0x01, MINECRAFT_1_12_1, true), - map(0x05, MINECRAFT_1_13, true)); + map(0x14, MINECRAFT_1_8, false), + map(0x01, MINECRAFT_1_9, false), + map(0x02, MINECRAFT_1_12, false), + map(0x01, MINECRAFT_1_12_1, false), + map(0x05, MINECRAFT_1_13, false)); SERVERBOUND.register(Chat.class, Chat::new, - map(0x01, MINECRAFT_1_8, true), - map(0x02, MINECRAFT_1_9, true), - map(0x03, MINECRAFT_1_12, true), - map(0x02, MINECRAFT_1_12_2, true), - map(0x02, MINECRAFT_1_13, true)); + map(0x01, MINECRAFT_1_8, false), + map(0x02, MINECRAFT_1_9, false), + map(0x03, MINECRAFT_1_12, false), + map(0x02, MINECRAFT_1_12_2, false), + map(0x02, MINECRAFT_1_13, false)); SERVERBOUND.register(ClientSettings.class, ClientSettings::new, - map(0x15, MINECRAFT_1_8, true), - map(0x04, MINECRAFT_1_9, true), - map(0x05, MINECRAFT_1_12, true), - map(0x04, MINECRAFT_1_12_1, true), - map(0x04, MINECRAFT_1_13, true)); + map(0x15, MINECRAFT_1_8, false), + map(0x04, MINECRAFT_1_9, false), + map(0x05, MINECRAFT_1_12, false), + map(0x04, MINECRAFT_1_12_1, false), + map(0x04, MINECRAFT_1_13, false)); SERVERBOUND.register(PluginMessage.class, PluginMessage::new, - map(0x17, MINECRAFT_1_8, true), - map(0x09, MINECRAFT_1_9, true), - map(0x0A, MINECRAFT_1_12, true), - map(0x09, MINECRAFT_1_12_1, true), - map(0x0A, MINECRAFT_1_13, true)); + map(0x17, MINECRAFT_1_8, false), + map(0x09, MINECRAFT_1_9, false), + map(0x0A, MINECRAFT_1_12, false), + map(0x09, MINECRAFT_1_12_1, false), + map(0x0A, MINECRAFT_1_13, false)); SERVERBOUND.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8, true), - map(0x0B, MINECRAFT_1_9, true), - map(0x0C, MINECRAFT_1_12, true), - map(0x0B, MINECRAFT_1_12_1, true), - map(0x0E, MINECRAFT_1_13, true)); + map(0x00, MINECRAFT_1_8, false), + map(0x0B, MINECRAFT_1_9, false), + map(0x0C, MINECRAFT_1_12, false), + map(0x0B, MINECRAFT_1_12_1, false), + map(0x0E, MINECRAFT_1_13, false)); CLIENTBOUND.register(BossBar.class, BossBar::new, - map(0x0C, MINECRAFT_1_9, true), - map(0x0C, MINECRAFT_1_12, true)); + map(0x0C, MINECRAFT_1_9, false), + map(0x0C, MINECRAFT_1_12, false)); CLIENTBOUND.register(Chat.class, Chat::new, - map(0x02, MINECRAFT_1_8, false), - map(0x0F, MINECRAFT_1_9, false), - map(0x0F, MINECRAFT_1_12, false), - map(0x0E, MINECRAFT_1_13, false)); + map(0x02, MINECRAFT_1_8, true), + map(0x0F, MINECRAFT_1_9, true), + map(0x0F, MINECRAFT_1_12, true), + map(0x0E, MINECRAFT_1_13, true)); CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, - map(0x3A, MINECRAFT_1_8, false), - map(0x0E, MINECRAFT_1_9, false), - map(0x0E, MINECRAFT_1_12, false), - map(0x10, MINECRAFT_1_13, false)); + map(0x3A, MINECRAFT_1_8, true), + map(0x0E, MINECRAFT_1_9, true), + map(0x0E, MINECRAFT_1_12, true), + map(0x10, MINECRAFT_1_13, true)); CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, - map(0x3F, MINECRAFT_1_8, true), - map(0x18, MINECRAFT_1_9, true), - map(0x18, MINECRAFT_1_12, true), - map(0x19, MINECRAFT_1_13, true)); + map(0x3F, MINECRAFT_1_8, false), + map(0x18, MINECRAFT_1_9, false), + map(0x18, MINECRAFT_1_12, false), + map(0x19, MINECRAFT_1_13, false)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, - map(0x40, MINECRAFT_1_8, true), - map(0x1A, MINECRAFT_1_9, true), - map(0x1A, MINECRAFT_1_12, true), - map(0x1B, MINECRAFT_1_13, true)); + map(0x40, MINECRAFT_1_8, false), + map(0x1A, MINECRAFT_1_9, false), + map(0x1A, MINECRAFT_1_12, false), + map(0x1B, MINECRAFT_1_13, false)); CLIENTBOUND.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8, true), - map(0x1F, MINECRAFT_1_9, true), - map(0x1F, MINECRAFT_1_12, true), - map(0x21, MINECRAFT_1_13, true)); + map(0x00, MINECRAFT_1_8, false), + map(0x1F, MINECRAFT_1_9, false), + map(0x1F, MINECRAFT_1_12, false), + map(0x21, MINECRAFT_1_13, false)); CLIENTBOUND.register(JoinGame.class, JoinGame::new, - map(0x01, MINECRAFT_1_8, true), - map(0x23, MINECRAFT_1_9, true), - map(0x23, MINECRAFT_1_12, true), - map(0x25, MINECRAFT_1_13, true)); + map(0x01, MINECRAFT_1_8, false), + map(0x23, MINECRAFT_1_9, false), + map(0x23, MINECRAFT_1_12, false), + map(0x25, MINECRAFT_1_13, false)); CLIENTBOUND.register(Respawn.class, Respawn::new, - map(0x07, MINECRAFT_1_8, false), - map(0x33, MINECRAFT_1_9, false), - map(0x34, MINECRAFT_1_12, false), - map(0x35, MINECRAFT_1_12_2, false), - map(0x38, MINECRAFT_1_13, false)); + map(0x07, MINECRAFT_1_8, true), + map(0x33, MINECRAFT_1_9, true), + map(0x34, MINECRAFT_1_12, true), + map(0x35, MINECRAFT_1_12_2, true), + map(0x38, MINECRAFT_1_13, true)); CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new, - map(0x3D, MINECRAFT_1_8, false), - map(0x38, MINECRAFT_1_9, false), - map(0x3A, MINECRAFT_1_12, false), - map(0x3B, MINECRAFT_1_12_1, false), - map(0x3E, MINECRAFT_1_13, false)); + map(0x3D, MINECRAFT_1_8, true), + map(0x38, MINECRAFT_1_9, true), + map(0x3A, MINECRAFT_1_12, true), + map(0x3B, MINECRAFT_1_12_1, true), + map(0x3E, MINECRAFT_1_13, true)); CLIENTBOUND.register(ScoreboardObjective.class, ScoreboardObjective::new, - map(0x3B, MINECRAFT_1_8, false), - map(0x3F, MINECRAFT_1_9, false), - map(0x41, MINECRAFT_1_12, false), - map(0x42, MINECRAFT_1_12_1, false), - map(0x45, MINECRAFT_1_13, false)); + map(0x3B, MINECRAFT_1_8, true), + map(0x3F, MINECRAFT_1_9, true), + map(0x41, MINECRAFT_1_12, true), + map(0x42, MINECRAFT_1_12_1, true), + map(0x45, MINECRAFT_1_13, true)); CLIENTBOUND.register(ScoreboardTeam.class, ScoreboardTeam::new, - map(0x3E, MINECRAFT_1_8, false), - map(0x41, MINECRAFT_1_9, false), - map(0x43, MINECRAFT_1_12, false), - map(0x44, MINECRAFT_1_12_1, false), - map(0x47, MINECRAFT_1_13, false)); + map(0x3E, MINECRAFT_1_8, true), + map(0x41, MINECRAFT_1_9, true), + map(0x43, MINECRAFT_1_12, true), + map(0x44, MINECRAFT_1_12_1, true), + map(0x47, MINECRAFT_1_13, true)); CLIENTBOUND.register(ScoreboardSetScore.class, ScoreboardSetScore::new, - map(0x3C, MINECRAFT_1_8, false), - map(0x42, MINECRAFT_1_9, false), - map(0x44, MINECRAFT_1_12, false), - map(0x45, MINECRAFT_1_12_1, false), - map(0x48, MINECRAFT_1_13, false)); + map(0x3C, MINECRAFT_1_8, true), + map(0x42, MINECRAFT_1_9, true), + map(0x44, MINECRAFT_1_12, true), + map(0x45, MINECRAFT_1_12_1, true), + map(0x48, MINECRAFT_1_13, true)); } }, LOGIN { @@ -138,7 +138,7 @@ public enum StateRegistry { SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, genericMappings(0x01)); SERVERBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new, - map(0x02, MINECRAFT_1_13, true)); + map(0x02, MINECRAFT_1_13, false)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, genericMappings(0x00)); @@ -149,7 +149,7 @@ public enum StateRegistry { CLIENTBOUND.register(SetCompression.class, SetCompression::new, genericMappings(0x03)); CLIENTBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new, - map(0x04, MINECRAFT_1_13, true)); + map(0x04, MINECRAFT_1_13, false)); } }; @@ -200,7 +200,7 @@ public enum StateRegistry { if (version == null) { throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion); } - if (mapping.packetDecoding) { + if (!mapping.encodeOnly) { version.packetIdToSupplier.put(mapping.id, packetSupplier); } version.packetClassToId.put(clazz, mapping.id); @@ -213,7 +213,7 @@ public enum StateRegistry { for (PacketMapping m : mappings) { if (linkedVersion == m.protocolVersion) continue links; } - register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.packetDecoding)); + register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.encodeOnly)); } } } @@ -261,12 +261,12 @@ public enum StateRegistry { public static class PacketMapping { private final int id; private final int protocolVersion; - private final boolean packetDecoding; + private final boolean encodeOnly; public PacketMapping(int id, int protocolVersion, boolean packetDecoding) { this.id = id; this.protocolVersion = protocolVersion; - this.packetDecoding = packetDecoding; + this.encodeOnly = packetDecoding; } @Override @@ -274,7 +274,7 @@ public enum StateRegistry { return "PacketMapping{" + "id=" + id + ", protocolVersion=" + protocolVersion + - ", packetDecoding=" + packetDecoding + + ", encodeOnly=" + encodeOnly + '}'; } @@ -285,25 +285,32 @@ public enum StateRegistry { PacketMapping that = (PacketMapping) o; return id == that.id && protocolVersion == that.protocolVersion && - packetDecoding == that.packetDecoding; + encodeOnly == that.encodeOnly; } @Override public int hashCode() { - return Objects.hash(id, protocolVersion, packetDecoding); + return Objects.hash(id, protocolVersion, encodeOnly); } } - private static PacketMapping map(int id, int version, boolean packetDecoding) { - return new PacketMapping(id, version, packetDecoding); + /** + * Creates a PacketMapping using the provided arguments + * @param id Packet Id + * @param version Protocol version + * @param encodeOnly When true packet decoding will be disabled + * @return PacketMapping with the provided arguments + */ + private static PacketMapping map(int id, int version, boolean encodeOnly) { + return new PacketMapping(id, version, encodeOnly); } private static PacketMapping[] genericMappings(int id) { return new PacketMapping[]{ - map(id, MINECRAFT_1_8, true), - map(id, MINECRAFT_1_9, true), - map(id, MINECRAFT_1_12, true), - map(id, MINECRAFT_1_13, true) + map(id, MINECRAFT_1_8, false), + map(id, MINECRAFT_1_9, false), + map(id, MINECRAFT_1_12, false), + map(id, MINECRAFT_1_13, false) }; } } 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 b4541786a..dac88e563 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.*; class PacketRegistryTest { private StateRegistry.PacketRegistry setupRegistry() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE); - registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, true)); + registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, false)); return registry; } @@ -44,8 +44,8 @@ class PacketRegistryTest { @Test void registrySuppliesCorrectPacketsByProtocol() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE); - registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, true), - new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, true)); + registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, false), + new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, false)); assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12).createPacket(0x00).getClass()); assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_1).createPacket(0x01).getClass()); assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_2).createPacket(0x01).getClass()); From a028467e66616f1f7eecf4757344a41d1c2c7750 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 20 Aug 2018 19:30:32 -0400 Subject: [PATCH 52/88] Plugin API (#34) The Velocity API has had a lot of community input (special thanks to @hugmanrique who started the work, @lucko who contributed permissions support, and @Minecrell for providing initial feedback and an initial version of ServerListPlus). While the API is far from complete, there is enough available for people to start doing useful stuff with Velocity. --- .gitignore | 1 + api/build.gradle | 39 +++ .../plugin/ap/PluginAnnotationProcessor.java | 87 +++++++ .../ap/SerializedPluginDescription.java | 125 +++++++++ .../javax.annotation.processing.Processor | 1 + .../velocitypowered/api/command/Command.java | 29 +++ .../api/command/CommandExecutor.java | 29 --- .../api/command/CommandInvoker.java | 23 -- .../api/command/CommandManager.java | 14 + .../api/command/CommandSource.java | 16 ++ .../api/event/EventHandler.java | 12 + .../api/event/EventManager.java | 75 ++++++ .../velocitypowered/api/event/PostOrder.java | 11 + .../api/event/ResultedEvent.java | 109 ++++++++ .../velocitypowered/api/event/Subscribe.java | 22 ++ .../connection/ConnectionHandshakeEvent.java | 27 ++ .../api/event/connection/DisconnectEvent.java | 28 ++ .../api/event/connection/LoginEvent.java | 41 +++ .../api/event/connection/PreLoginEvent.java | 49 ++++ .../permission/PermissionsSetupEvent.java | 64 +++++ .../event/player/ServerConnectedEvent.java | 35 +++ .../event/player/ServerPreConnectEvent.java | 86 +++++++ .../api/event/proxy/ProxyInitializeEvent.java | 11 + .../api/event/proxy/ProxyPingEvent.java | 37 +++ .../api/event/proxy/ProxyShutdownEvent.java | 12 + .../api/permission/PermissionFunction.java | 33 +++ .../api/permission/PermissionProvider.java | 17 ++ .../api/permission/PermissionSubject.java | 16 ++ .../api/permission/Tristate.java | 74 ++++++ .../api/plugin/Dependency.java | 30 +++ .../api/plugin/InvalidPluginException.java | 19 ++ .../velocitypowered/api/plugin/Plugin.java | 44 ++++ .../api/plugin/PluginContainer.java | 26 ++ .../api/plugin/PluginDescription.java | 71 ++++++ .../api/plugin/PluginManager.java | 55 ++++ .../api/plugin/annotation/DataDirectory.java | 18 ++ .../api/plugin/meta/PluginDependency.java | 79 ++++++ .../api/proxy/ConnectionRequestBuilder.java | 5 +- .../api/proxy/InboundConnection.java | 7 + .../com/velocitypowered/api/proxy/Player.java | 12 +- .../api/proxy/ProxyServer.java | 40 ++- .../api/scheduler/ScheduledTask.java | 12 + .../api/scheduler/Scheduler.java | 22 ++ .../api/scheduler/TaskStatus.java | 7 + .../velocitypowered/api/server/Favicon.java | 9 +- .../api/server/ServerInfo.java | 11 +- .../api/server/ServerPing.java | 240 ++++++++++++++++++ .../api/util}/GameProfile.java | 10 +- .../api/util/LegacyChatColorUtils.java | 5 +- .../velocitypowered/api}/util/UuidUtils.java | 15 +- build.gradle | 3 +- proxy/build.gradle | 2 + .../velocitypowered/proxy/VelocityServer.java | 97 ++++++- .../proxy/command/CommandManager.java | 77 ------ .../proxy/command/ServerCommand.java | 16 +- .../proxy/command/ShutdownCommand.java | 12 +- .../proxy/command/VelocityCommand.java | 14 +- .../proxy/command/VelocityCommandManager.java | 85 +++++++ .../backend/BackendPlaySessionHandler.java | 10 +- .../backend/LoginSessionHandler.java | 2 +- .../client/ClientPlaySessionHandler.java | 12 +- .../connection/client/ConnectedPlayer.java | 42 ++- .../client/HandshakeSessionHandler.java | 36 ++- .../client/InitialInboundConnection.java | 38 +++ .../client/LoginSessionHandler.java | 78 ++++-- .../client/StatusSessionHandler.java | 24 +- .../proxy/console/VelocityConsole.java | 13 +- .../proxy/data/ServerPing.java | 96 ------- .../proxy/plugin/PluginClassLoader.java | 56 ++++ .../proxy/plugin/VelocityEventManager.java | 192 ++++++++++++++ .../proxy/plugin/VelocityPluginManager.java | 129 ++++++++++ .../proxy/plugin/loader/JavaPluginLoader.java | 129 ++++++++++ .../proxy/plugin/loader/PluginLoader.java | 18 ++ .../loader/VelocityPluginContainer.java | 28 ++ .../loader/VelocityPluginDescription.java | 69 +++++ .../java/JavaVelocityPluginDescription.java | 22 ++ .../java/SerializedPluginDescription.java | 125 +++++++++ .../loader/java/VelocityPluginModule.java | 38 +++ .../plugin/util/PluginDependencyUtils.java | 69 +++++ .../protocol/packet/LegacyPingResponse.java | 2 +- .../proxy/scheduler/Sleeper.java | 7 + .../proxy/scheduler/VelocityScheduler.java | 176 +++++++++++++ .../ThreadRecorderThreadFactory.java | 42 +++ proxy/src/main/resources/log4j2.xml | 8 +- .../scheduler/VelocitySchedulerTest.java | 51 ++++ .../proxy/testutil/FakePluginManager.java | 92 +++++++ .../proxy/util/UuidUtilsTest.java | 1 + .../ThreadRecorderThreadFactoryTest.java | 36 +++ settings.gradle | 4 +- 89 files changed, 3453 insertions(+), 358 deletions(-) create mode 100644 api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java create mode 100644 api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java create mode 100644 api/src/ap/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 api/src/main/java/com/velocitypowered/api/command/Command.java delete mode 100644 api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java delete mode 100644 api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java create mode 100644 api/src/main/java/com/velocitypowered/api/command/CommandManager.java create mode 100644 api/src/main/java/com/velocitypowered/api/command/CommandSource.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/EventHandler.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/EventManager.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/PostOrder.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/Subscribe.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java create mode 100644 api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java create mode 100644 api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java create mode 100644 api/src/main/java/com/velocitypowered/api/permission/Tristate.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/Dependency.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/Plugin.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java create mode 100644 api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java create mode 100644 api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java create mode 100644 api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java create mode 100644 api/src/main/java/com/velocitypowered/api/server/ServerPing.java rename {proxy/src/main/java/com/velocitypowered/proxy/data => api/src/main/java/com/velocitypowered/api/util}/GameProfile.java (83%) rename {proxy/src/main/java/com/velocitypowered/proxy => api/src/main/java/com/velocitypowered/api}/util/UuidUtils.java (63%) delete mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java delete mode 100644 proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/scheduler/Sleeper.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java diff --git a/.gitignore b/.gitignore index dc127090a..3fc46f0ef 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf +.idea/**/compiler.xml # Sensitive or high-churn files .idea/**/dataSources/ diff --git a/api/build.gradle b/api/build.gradle index 4f14afb33..18ec5eb48 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,6 +1,13 @@ plugins { id 'java' id 'com.github.johnrengelman.shadow' version '2.0.4' + id 'maven-publish' +} + +sourceSets { + ap { + compileClasspath += main.compileClasspath + main.output + } } dependencies { @@ -8,6 +15,10 @@ dependencies { compile "com.google.guava:guava:${guavaVersion}" compile 'net.kyori:text:1.12-1.6.4' compile 'com.moandjiezana.toml:toml4j:0.7.2' + compile "org.slf4j:slf4j-api:${slf4jVersion}" + compile 'com.google.inject:guice:4.2.0' + compile 'org.checkerframework:checker-qual:2.5.4' + testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" } @@ -20,6 +31,15 @@ task javadocJar(type: Jar) { task sourcesJar(type: Jar) { classifier 'sources' from sourceSets.main.allSource + from sourceSets.ap.output +} + +jar { + from sourceSets.ap.output +} + +shadowJar { + from sourceSets.ap.output } artifacts { @@ -27,3 +47,22 @@ artifacts { archives shadowJar archives sourcesJar } + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + + artifact sourcesJar + artifact javadocJar + } + } + + // TODO: Set up a Maven repository on Velocity's infrastructure, preferably something lightweight. + /*repositories { + maven { + name = 'myRepo' + url = "file://${buildDir}/repo" + } + }*/ +} 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 new file mode 100644 index 000000000..a00f9c7d3 --- /dev/null +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java @@ -0,0 +1,87 @@ +package com.velocitypowered.api.plugin.ap; + +import com.google.gson.Gson; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginDescription; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.*; + +@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"}) +public class PluginAnnotationProcessor extends AbstractProcessor { + private ProcessingEnvironment environment; + private String pluginClassFound; + private boolean warnedAboutMultiplePlugins; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + this.environment = processingEnv; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return false; + } + + for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) { + if (element.getKind() != ElementKind.CLASS) { + environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with " + + Plugin.class.getCanonicalName()); + return false; + } + + Name qualifiedName = ((TypeElement) element).getQualifiedName(); + + if (Objects.equals(pluginClassFound, qualifiedName.toString())) { + if (!warnedAboutMultiplePlugins) { + environment.getMessager().printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " + + "multiple plugins. We are using " + pluginClassFound + " for your plugin's main class."); + warnedAboutMultiplePlugins = true; + } + return false; + } + + Plugin plugin = element.getAnnotation(Plugin.class); + if (!PluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) { + environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin " + + qualifiedName + ". IDs must start alphabetically, have alphanumeric characters, and can " + + "contain dashes or underscores."); + return false; + } + + // All good, generate the velocity-plugin.json. + SerializedPluginDescription description = SerializedPluginDescription.from(plugin, qualifiedName.toString()); + try { + FileObject object = environment.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json"); + try (Writer writer = new BufferedWriter(object.openWriter())) { + new Gson().toJson(description, writer); + } + pluginClassFound = qualifiedName.toString(); + } catch (IOException e) { + environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file"); + } + } + + return false; + } +} diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java new file mode 100644 index 000000000..d22aeeb16 --- /dev/null +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java @@ -0,0 +1,125 @@ +package com.velocitypowered.api.plugin.ap; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class SerializedPluginDescription { + private final String id; + private final String author; + private final String main; + private final String version; + private final List dependencies; + + public SerializedPluginDescription(String id, String author, String main, String version) { + this(id, author, main, version, ImmutableList.of()); + } + + public SerializedPluginDescription(String id, String author, String main, String version, List dependencies) { + this.id = Preconditions.checkNotNull(id, "id"); + this.author = Preconditions.checkNotNull(author, "author"); + this.main = Preconditions.checkNotNull(main, "main"); + this.version = Preconditions.checkNotNull(version, "version"); + this.dependencies = ImmutableList.copyOf(dependencies); + } + + public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { + List dependencies = new ArrayList<>(); + for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { + dependencies.add(new Dependency(dependency.id(), dependency.optional())); + } + return new SerializedPluginDescription(plugin.id(), plugin.author(), qualifiedName, plugin.version(), dependencies); + } + + public String getId() { + return id; + } + + public String getAuthor() { + return author; + } + + public String getMain() { + return main; + } + + public String getVersion() { + return version; + } + + public List getDependencies() { + return dependencies; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SerializedPluginDescription that = (SerializedPluginDescription) o; + return Objects.equals(id, that.id) && + Objects.equals(author, that.author) && + Objects.equals(main, that.main) && + Objects.equals(version, that.version) && + Objects.equals(dependencies, that.dependencies); + } + + @Override + public int hashCode() { + return Objects.hash(id, author, main, version, dependencies); + } + + @Override + public String toString() { + return "SerializedPluginDescription{" + + "id='" + id + '\'' + + ", author='" + author + '\'' + + ", main='" + main + '\'' + + ", version='" + version + '\'' + + ", dependencies=" + dependencies + + '}'; + } + + public static class Dependency { + private final String id; + private final boolean optional; + + public Dependency(String id, boolean optional) { + this.id = id; + this.optional = optional; + } + + public String getId() { + return id; + } + + public boolean isOptional() { + return optional; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Dependency that = (Dependency) o; + return optional == that.optional && + Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id, optional); + } + + @Override + public String toString() { + return "Dependency{" + + "id='" + id + '\'' + + ", optional=" + optional + + '}'; + } + } +} diff --git a/api/src/ap/resources/META-INF/services/javax.annotation.processing.Processor b/api/src/ap/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000..a96abf5d2 --- /dev/null +++ b/api/src/ap/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/command/Command.java b/api/src/main/java/com/velocitypowered/api/command/Command.java new file mode 100644 index 000000000..d1cfd9f49 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/command/Command.java @@ -0,0 +1,29 @@ +package com.velocitypowered.api.command; + +import com.google.common.collect.ImmutableList; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; + +/** + * Represents a command that can be executed by a {@link CommandSource}, such as a {@link com.velocitypowered.api.proxy.Player} + * or the console. + */ +public interface Command { + /** + * Executes the command for the specified {@link CommandSource}. + * @param source the source of this command + * @param args the arguments for this command + */ + void execute(@NonNull CommandSource source, @NonNull String[] args); + + /** + * Provides tab complete suggestions for a command for a specified {@link CommandSource}. + * @param source the source to run the command for + * @param currentArgs the current, partial arguments for this command + * @return tab complete suggestions + */ + default List suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) { + return ImmutableList.of(); + } +} diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java b/api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java deleted file mode 100644 index 22c6eb765..000000000 --- a/api/src/main/java/com/velocitypowered/api/command/CommandExecutor.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.velocitypowered.api.command; - -import com.google.common.collect.ImmutableList; - -import javax.annotation.Nonnull; -import java.util.List; - -/** - * Represents a command that can be executed by a {@link CommandInvoker}, such as a {@link com.velocitypowered.api.proxy.Player} - * or the console. - */ -public interface CommandExecutor { - /** - * Executes the command for the specified {@link CommandInvoker}. - * @param invoker the invoker of this command - * @param args the arguments for this command - */ - void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args); - - /** - * Provides tab complete suggestions for a command for a specified {@link CommandInvoker}. - * @param invoker the invoker to run the command for - * @param currentArgs the current, partial arguments for this command - * @return tab complete suggestions - */ - default List suggest(@Nonnull CommandInvoker invoker, @Nonnull String[] currentArgs) { - return ImmutableList.of(); - } -} diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java b/api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java deleted file mode 100644 index 22230dcb9..000000000 --- a/api/src/main/java/com/velocitypowered/api/command/CommandInvoker.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.velocitypowered.api.command; - -import net.kyori.text.Component; - -import javax.annotation.Nonnull; - -/** - * Represents something that can be used to run a {@link CommandExecutor}. - */ -public interface CommandInvoker { - /** - * Sends the specified {@code component} to the invoker. - * @param component the text component to send - */ - void sendMessage(@Nonnull Component component); - - /** - * Determines whether or not the invoker has a particular permission. - * @param permission the permission to check for - * @return whether or not the invoker has permission to run this command - */ - boolean hasPermission(@Nonnull String permission); -} diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java new file mode 100644 index 000000000..38f031716 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -0,0 +1,14 @@ +package com.velocitypowered.api.command; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents an interface to register a command executor with the proxy. + */ +public interface CommandManager { + void register(@NonNull Command command, String... aliases); + + void unregister(@NonNull String alias); + + boolean execute(@NonNull CommandSource source, @NonNull String cmdLine); +} diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandSource.java b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java new file mode 100644 index 000000000..70c11318e --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java @@ -0,0 +1,16 @@ +package com.velocitypowered.api.command; + +import com.velocitypowered.api.permission.PermissionSubject; +import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents something that can be used to run a {@link Command}. + */ +public interface CommandSource extends PermissionSubject { + /** + * Sends the specified {@code component} to the invoker. + * @param component the text component to send + */ + void sendMessage(@NonNull Component component); +} diff --git a/api/src/main/java/com/velocitypowered/api/event/EventHandler.java b/api/src/main/java/com/velocitypowered/api/event/EventHandler.java new file mode 100644 index 000000000..146239cf7 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/EventHandler.java @@ -0,0 +1,12 @@ +package com.velocitypowered.api.event; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents an interface to perform direct dispatch of an event. This makes integration easier to achieve with platforms + * such as RxJava. + */ +@FunctionalInterface +public interface EventHandler { + void execute(@NonNull E event); +} diff --git a/api/src/main/java/com/velocitypowered/api/event/EventManager.java b/api/src/main/java/com/velocitypowered/api/event/EventManager.java new file mode 100644 index 000000000..28b197e55 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/EventManager.java @@ -0,0 +1,75 @@ +package com.velocitypowered.api.event; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.concurrent.CompletableFuture; + +/** + * Allows plugins to register and deregister listeners for event handlers. + */ +public interface EventManager { + /** + * Requests that the specified {@code listener} listen for events and associate it with the {@code plugin}. + * @param plugin the plugin to associate with the listener + * @param listener the listener to register + */ + void register(@NonNull Object plugin, @NonNull Object listener); + + /** + * Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. + * @param plugin the plugin to associate with the handler + * @param eventClass the class for the event handler to register + * @param handler the handler to register + * @param the event type to handle + */ + default void register(@NonNull Object plugin, @NonNull Class eventClass, @NonNull EventHandler handler) { + register(plugin, eventClass, PostOrder.NORMAL, handler); + } + + /** + * Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. + * @param plugin the plugin to associate with the handler + * @param eventClass the class for the event handler to register + * @param postOrder the order in which events should be posted to the handler + * @param handler the handler to register + * @param the event type to handle + */ + void register(@NonNull Object plugin, @NonNull Class eventClass, @NonNull PostOrder postOrder, @NonNull EventHandler handler); + + /** + * Fires the specified event to the event bus asynchronously. This allows Velocity to continue servicing connections + * while a plugin handles a potentially long-running operation such as a database query. + * @param event the event to fire + * @return a {@link CompletableFuture} representing the posted event + */ + @NonNull CompletableFuture fire(@NonNull E event); + + /** + * Posts the specified event to the event bus and discards the result. + * @param event the event to fire + */ + default void fireAndForget(@NonNull Object event) { + fire(event); + } + + /** + * Unregisters all listeners for the specified {@code plugin}. + * @param plugin the plugin to deregister listeners for + */ + void unregisterListeners(@NonNull Object plugin); + + /** + * Unregisters a specific listener for a specific plugin. + * @param plugin the plugin associated with the listener + * @param listener the listener to deregister + */ + void unregisterListener(@NonNull Object plugin, @NonNull Object listener); + + /** + * Unregisters a specific event handler for a specific plugin. + * @param plugin the plugin to associate with the handler + * @param handler the handler to register + * @param the event type to handle + */ + void unregister(@NonNull Object plugin, @NonNull EventHandler handler); +} diff --git a/api/src/main/java/com/velocitypowered/api/event/PostOrder.java b/api/src/main/java/com/velocitypowered/api/event/PostOrder.java new file mode 100644 index 000000000..a9bffe331 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/PostOrder.java @@ -0,0 +1,11 @@ +package com.velocitypowered.api.event; + +/** + * Represents the order an event will be posted to a listener method, relative + * to other listeners. + */ +public enum PostOrder { + + FIRST, EARLY, NORMAL, LATE, LAST; + +} diff --git a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java new file mode 100644 index 000000000..04f28f390 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -0,0 +1,109 @@ +package com.velocitypowered.api.event; + +import com.google.common.base.Preconditions; +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; + +/** + * Indicates an event that has a result attached to it. + */ +public interface ResultedEvent { + /** + * Returns the result associated with this event. + * @return the result of this event + */ + R getResult(); + + /** + * Sets the result of this event. + * @param result the new result + */ + void setResult(@NonNull R result); + + /** + * Represents a result for an event. + */ + interface Result { + boolean isAllowed(); + } + + /** + * A generic "allowed/denied" result. + */ + class GenericResult implements Result { + private static final GenericResult ALLOWED = new GenericResult(true); + private static final GenericResult DENIED = new GenericResult(true); + + private final boolean allowed; + + private GenericResult(boolean b) { + this.allowed = b; + } + + @Override + public boolean isAllowed() { + return allowed; + } + + @Override + public String toString() { + return allowed ? "allowed" : "denied"; + } + + public static GenericResult allowed() { + return ALLOWED; + } + + public static GenericResult denied() { + return DENIED; + } + } + + /** + * Represents an "allowed/denied" result with a reason allowed for denial. + */ + class ComponentResult implements Result { + private static final ComponentResult ALLOWED = new ComponentResult(true, null); + + private final boolean allowed; + private final @Nullable Component reason; + + private ComponentResult(boolean allowed, @Nullable Component reason) { + this.allowed = allowed; + this.reason = reason; + } + + @Override + public boolean isAllowed() { + return allowed; + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + + @Override + public String toString() { + if (allowed) { + return "allowed"; + } + if (reason != null) { + return "denied: " + ComponentSerializers.PLAIN.serialize(reason); + } + return "denied"; + } + + public static ComponentResult allowed() { + return ALLOWED; + } + + public static ComponentResult denied(@NonNull Component reason) { + Preconditions.checkNotNull(reason, "reason"); + return new ComponentResult(false, reason); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/Subscribe.java b/api/src/main/java/com/velocitypowered/api/event/Subscribe.java new file mode 100644 index 000000000..adb45b1ca --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/Subscribe.java @@ -0,0 +1,22 @@ +package com.velocitypowered.api.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that indicates that this method can be used to listen for an event from the proxy. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Subscribe { + + /** + * The order events will be posted to this listener. + * + * @return the order + */ + PostOrder order() default PostOrder.NORMAL; + +} diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java new file mode 100644 index 000000000..f2ecc0547 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java @@ -0,0 +1,27 @@ +package com.velocitypowered.api.event.connection; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.InboundConnection; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * This event is fired when a handshake is established between a client and Velocity. + */ +public class ConnectionHandshakeEvent { + private final @NonNull InboundConnection connection; + + public ConnectionHandshakeEvent(@NonNull InboundConnection connection) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + } + + public InboundConnection getConnection() { + return connection; + } + + @Override + public String toString() { + return "ConnectionHandshakeEvent{" + + "connection=" + connection + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java new file mode 100644 index 000000000..0ef43909b --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java @@ -0,0 +1,28 @@ +package com.velocitypowered.api.event.connection; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.Player; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * This event is fired when a player disconnects from the proxy. Operations on the provided player, aside from basic + * data retrieval operations, may behave in undefined ways. + */ +public class DisconnectEvent { + private @NonNull final Player player; + + public DisconnectEvent(@NonNull Player player) { + this.player = Preconditions.checkNotNull(player, "player"); + } + + public Player getPlayer() { + return player; + } + + @Override + public String toString() { + return "DisconnectEvent{" + + "player=" + player + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java new file mode 100644 index 000000000..0ac7f9374 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java @@ -0,0 +1,41 @@ +package com.velocitypowered.api.event.connection; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.proxy.Player; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * This event is fired once the player has been authenticated but before they connect to a server on the proxy. + */ +public class LoginEvent implements ResultedEvent { + private final Player player; + private ComponentResult result; + + public LoginEvent(@NonNull Player player) { + this.player = Preconditions.checkNotNull(player, "player"); + this.result = ComponentResult.allowed(); + } + + public Player getPlayer() { + return player; + } + + @Override + public ComponentResult getResult() { + return result; + } + + @Override + public void setResult(@NonNull ComponentResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "LoginEvent{" + + "player=" + player + + ", result=" + result + + '}'; + } +} 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 new file mode 100644 index 000000000..ad0c9c4a3 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java @@ -0,0 +1,49 @@ +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 org.checkerframework.checker.nullness.qual.NonNull; + +/** + * 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). + */ +public class PreLoginEvent implements ResultedEvent { + private final InboundConnection connection; + private final String username; + private ComponentResult result; + + public PreLoginEvent(InboundConnection connection, String username) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.username = Preconditions.checkNotNull(username, "username"); + this.result = ComponentResult.allowed(); + } + + public InboundConnection getConnection() { + return connection; + } + + public String getUsername() { + return username; + } + + @Override + public ComponentResult getResult() { + return result; + } + + @Override + public void setResult(@NonNull ComponentResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "PreLoginEvent{" + + "connection=" + connection + + ", username='" + username + '\'' + + ", result=" + result + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java b/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java new file mode 100644 index 000000000..de3976025 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java @@ -0,0 +1,64 @@ +package com.velocitypowered.api.event.permission; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.permission.PermissionProvider; +import com.velocitypowered.api.permission.PermissionSubject; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Called when a {@link PermissionSubject}'s permissions are being setup. + * + *

This event is only called once per subject, on initialisation.

+ */ +public class PermissionsSetupEvent { + private final PermissionSubject subject; + private final PermissionProvider defaultProvider; + private PermissionProvider provider; + + public PermissionsSetupEvent(PermissionSubject subject, PermissionProvider provider) { + this.subject = Preconditions.checkNotNull(subject, "subject"); + this.provider = this.defaultProvider = Preconditions.checkNotNull(provider, "provider"); + } + + public @NonNull PermissionSubject getSubject() { + return this.subject; + } + + /** + * Uses the provider function to obtain a {@link PermissionFunction} for + * the subject. + * + * @param subject the subject + * @return the obtained permission function + */ + public @NonNull PermissionFunction createFunction(PermissionSubject subject) { + return this.provider.createFunction(subject); + } + + public @NonNull PermissionProvider getProvider() { + return this.provider; + } + + /** + * Sets the {@link PermissionFunction} that should be used for the subject. + * + *

Specifying null will reset the provider to the default + * instance given when the event was posted.

+ * + * @param provider the provider + */ + public void setProvider(@Nullable PermissionProvider provider) { + this.provider = provider == null ? this.defaultProvider : provider; + } + + @Override + public String toString() { + return "PermissionsSetupEvent{" + + "subject=" + subject + + ", defaultProvider=" + defaultProvider + + ", provider=" + provider + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java new file mode 100644 index 000000000..1c7c3f416 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java @@ -0,0 +1,35 @@ +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.server.ServerInfo; + +/** + * This event is fired once the player has successfully connected to the target server and the connection to the previous + * server has been de-established. + */ +public class ServerConnectedEvent { + private final Player player; + private final ServerInfo server; + + public ServerConnectedEvent(Player player, ServerInfo server) { + this.player = Preconditions.checkNotNull(player, "player"); + this.server = Preconditions.checkNotNull(server, "server"); + } + + public Player getPlayer() { + return player; + } + + public ServerInfo getServer() { + return server; + } + + @Override + public String toString() { + return "ServerConnectedEvent{" + + "player=" + player + + ", server=" + server + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java new file mode 100644 index 000000000..a80ce70e3 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java @@ -0,0 +1,86 @@ +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.server.ServerInfo; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Optional; + +/** + * This event is fired before the player connects to a server. + */ +public class ServerPreConnectEvent implements ResultedEvent { + private final Player player; + private ServerResult result; + + public ServerPreConnectEvent(Player player, ServerResult result) { + this.player = Preconditions.checkNotNull(player, "player"); + this.result = Preconditions.checkNotNull(result, "result"); + } + + public Player getPlayer() { + return player; + } + + @Override + public ServerResult getResult() { + return result; + } + + @Override + public void setResult(@NonNull ServerResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "ServerPreConnectEvent{" + + "player=" + player + + ", result=" + result + + '}'; + } + + /** + * Represents the result of the {@link ServerPreConnectEvent}. + */ + public static class ServerResult implements ResultedEvent.Result { + private static final ServerResult DENIED = new ServerResult(false, null); + + private final boolean allowed; + private final ServerInfo info; + + private ServerResult(boolean allowed, @Nullable ServerInfo info) { + this.allowed = allowed; + this.info = info; + } + + @Override + public boolean isAllowed() { + return allowed; + } + + public Optional getInfo() { + return Optional.ofNullable(info); + } + + @Override + public String toString() { + if (!allowed) { + return "denied"; + } + return "allowed: connect to " + info.getName(); + } + + public static ServerResult denied() { + return DENIED; + } + + public static ServerResult allowed(ServerInfo server) { + Preconditions.checkNotNull(server, "server"); + return new ServerResult(true, server); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java new file mode 100644 index 000000000..14bee8b1e --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java @@ -0,0 +1,11 @@ +package com.velocitypowered.api.event.proxy; + +/** + * This event is fired by the proxy after plugins have been loaded but before the proxy starts accepting connections. + */ +public class ProxyInitializeEvent { + @Override + public String toString() { + return "ProxyInitializeEvent"; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java new file mode 100644 index 000000000..53a7531cc --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java @@ -0,0 +1,37 @@ +package com.velocitypowered.api.event.proxy; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.server.ServerPing; + +import javax.annotation.Nonnull; + +public class ProxyPingEvent { + private final InboundConnection connection; + private ServerPing ping; + + public ProxyPingEvent(InboundConnection connection, ServerPing ping) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.ping = Preconditions.checkNotNull(ping, "ping"); + } + + public InboundConnection getConnection() { + return connection; + } + + public ServerPing getPing() { + return ping; + } + + public void setPing(@Nonnull ServerPing ping) { + this.ping = Preconditions.checkNotNull(ping, "ping"); + } + + @Override + public String toString() { + return "ProxyPingEvent{" + + "connection=" + connection + + ", ping=" + ping + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java new file mode 100644 index 000000000..0ae15f192 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java @@ -0,0 +1,12 @@ +package com.velocitypowered.api.event.proxy; + +/** + * This event is fired by the proxy after the proxy has stopped accepting connections but before the proxy process + * exits. + */ +public class ProxyShutdownEvent { + @Override + public String toString() { + return "ProxyShutdownEvent"; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java b/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java new file mode 100644 index 000000000..102e36eba --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java @@ -0,0 +1,33 @@ +package com.velocitypowered.api.permission; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Function that calculates the permission settings for a given + * {@link PermissionSubject}. + */ +@FunctionalInterface +public interface PermissionFunction { + /** + * A permission function that always returns {@link Tristate#TRUE}. + */ + PermissionFunction ALWAYS_TRUE = p -> Tristate.TRUE; + + /** + * A permission function that always returns {@link Tristate#FALSE}. + */ + PermissionFunction ALWAYS_FALSE = p -> Tristate.FALSE; + + /** + * A permission function that always returns {@link Tristate#UNDEFINED}. + */ + PermissionFunction ALWAYS_UNDEFINED = p -> Tristate.UNDEFINED; + + /** + * Gets the subjects setting for a particular permission. + * + * @param permission the permission + * @return the value the permission is set to + */ + @NonNull Tristate getPermissionSetting(@NonNull String permission); +} diff --git a/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java b/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java new file mode 100644 index 000000000..fc040330c --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java @@ -0,0 +1,17 @@ +package com.velocitypowered.api.permission; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Provides {@link PermissionFunction}s for {@link PermissionSubject}s. + */ +@FunctionalInterface +public interface PermissionProvider { + /** + * Creates a {@link PermissionFunction} for the subject. + * + * @param subject the subject + * @return the function + */ + @NonNull PermissionFunction createFunction(@NonNull PermissionSubject subject); +} diff --git a/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java b/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java new file mode 100644 index 000000000..8377f0729 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java @@ -0,0 +1,16 @@ +package com.velocitypowered.api.permission; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents a object that has a set of queryable permissions. + */ +public interface PermissionSubject { + /** + * Determines whether or not the subject has a particular permission. + * + * @param permission the permission to check for + * @return whether or not the subject has the permission + */ + boolean hasPermission(@NonNull String permission); +} diff --git a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java new file mode 100644 index 000000000..698a5f95b --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java @@ -0,0 +1,74 @@ +package com.velocitypowered.api.permission; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Represents three different states of a setting. + * + *

Possible values:

+ *

+ *
    + *
  • {@link #TRUE} - a positive setting
  • + *
  • {@link #FALSE} - a negative (negated) setting
  • + *
  • {@link #UNDEFINED} - a non-existent setting
  • + *
+ */ +public enum Tristate { + + /** + * A value indicating a positive setting + */ + TRUE(true), + + /** + * A value indicating a negative (negated) setting + */ + FALSE(false), + + /** + * A value indicating a non-existent setting + */ + UNDEFINED(false); + + /** + * Returns a {@link Tristate} from a boolean + * + * @param val the boolean value + * @return {@link #TRUE} or {@link #FALSE}, if the value is true or false, respectively. + */ + public static @NonNull Tristate fromBoolean(boolean val) { + return val ? TRUE : FALSE; + } + + /** + * Returns a {@link Tristate} from a nullable boolean. + * + *

Unlike {@link #fromBoolean(boolean)}, this method returns {@link #UNDEFINED} + * if the value is null.

+ * + * @param val the boolean value + * @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value + * is null, true or false, respectively. + */ + public static @NonNull Tristate fromNullableBoolean(@Nullable Boolean val) { + return val == null ? UNDEFINED : val ? TRUE : FALSE; + } + + private final boolean booleanValue; + + Tristate(boolean booleanValue) { + this.booleanValue = booleanValue; + } + + /** + * Returns the value of the Tristate as a boolean. + * + *

A value of {@link #UNDEFINED} converts to false.

+ * + * @return a boolean representation of the Tristate. + */ + public boolean asBoolean() { + return this.booleanValue; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java b/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java new file mode 100644 index 000000000..6af5d11e7 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java @@ -0,0 +1,30 @@ +package com.velocitypowered.api.plugin; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents a dependency for a {@link Plugin} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({}) +public @interface Dependency { + /** + * The plugin ID of the dependency. + * + * @return The dependency plugin ID + * @see Plugin#id() + */ + String id(); + + // TODO Add required version field + + /** + * If this dependency is optional for the plugin to work. By default + * this is {@code false}. + * + * @return true if the dependency is optional for the plugin to work + */ + boolean optional() default false; +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java b/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java new file mode 100644 index 000000000..bd9bdad24 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java @@ -0,0 +1,19 @@ +package com.velocitypowered.api.plugin; + +public class InvalidPluginException extends Exception { + public InvalidPluginException() { + super(); + } + + public InvalidPluginException(String message) { + super(message); + } + + public InvalidPluginException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidPluginException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java new file mode 100644 index 000000000..4f7ab93ed --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java @@ -0,0 +1,44 @@ +package com.velocitypowered.api.plugin; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to describe a Velocity plugin. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Plugin { + /** + * The ID of the plugin. This ID should be unique as to + * not conflict with other plugins. + * + * The plugin ID must match the {@link PluginDescription#ID_PATTERN}. + * + * @return the ID for this plugin + */ + String id(); + + /** + * The version of the plugin. + * + * @return the version of the plugin, or an empty string if unknown + */ + String version() default ""; + + /** + * The author of the plugin. + * + * @return the plugin's author, or empty if unknown + */ + String author() default ""; + + /** + * The dependencies required to load before this plugin. + * + * @return the plugin dependencies + */ + Dependency[] dependencies() default {}; +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java new file mode 100644 index 000000000..6ff5a6fee --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java @@ -0,0 +1,26 @@ +package com.velocitypowered.api.plugin; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Optional; + +/** + * A wrapper around a plugin loaded by the proxy. + */ +public interface PluginContainer { + /** + * Returns the plugin's description. + * + * @return the plugin's description + */ + @NonNull PluginDescription getDescription(); + + /** + * Returns the created plugin if it is available. + * + * @return the instance if available + */ + default Optional getInstance() { + return Optional.empty(); + } +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java new file mode 100644 index 000000000..a61274e8d --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java @@ -0,0 +1,71 @@ +package com.velocitypowered.api.plugin; + +import com.google.common.collect.ImmutableSet; +import com.velocitypowered.api.plugin.meta.PluginDependency; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Represents metadata for a specific version of a plugin. + */ +public interface PluginDescription { + /** + * The pattern plugin IDs must match. Plugin IDs may only contain + * alphanumeric characters, dashes or underscores, must start with + * an alphabetic character and cannot be longer than 64 characters. + */ + Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}"); + + /** + * Gets the qualified ID of the {@link Plugin} within this container. + * + * @return the plugin ID + * @see Plugin#id() + */ + String getId(); + + /** + * Gets the version of the {@link Plugin} within this container. + * + * @return the plugin version + * @see Plugin#version() + */ + String getVersion(); + + /** + * Gets the author of the {@link Plugin} within this container. + * + * @return the plugin author + * @see Plugin#author() + */ + String getAuthor(); + + /** + * Gets a {@link Collection} of all dependencies of the {@link Plugin} within + * this container. + * + * @return the plugin dependencies, can be empty + * @see Plugin#dependencies() + */ + default Collection getDependencies() { + return ImmutableSet.of(); + } + + default Optional getDependency(String id) { + return Optional.empty(); + } + + /** + * Returns the source the plugin was loaded from. + * + * @return the source the plugin was loaded from or {@link Optional#empty()} + * if unknown + */ + default Optional getSource() { + return Optional.empty(); + } +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java new file mode 100644 index 000000000..ba015d0a6 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java @@ -0,0 +1,55 @@ +package com.velocitypowered.api.plugin; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * The class that manages plugins. This manager can retrieve + * {@link PluginContainer}s from {@link Plugin} instances, getting + * {@link Logger}s, etc. + */ +public interface PluginManager { + /** + * Gets the plugin container from an instance. + * + * @param instance the instance + * @return the container + */ + @NonNull Optional fromInstance(@NonNull Object instance); + + /** + * Retrieves a {@link PluginContainer} based on its ID. + * + * @param id the plugin ID + * @return the plugin, if available + */ + @NonNull Optional getPlugin(@NonNull String id); + + /** + * Gets a {@link Collection} of all {@link PluginContainer}s. + * + * @return the plugins + */ + @NonNull Collection getPlugins(); + + /** + * Checks if a plugin is loaded based on its ID. + * + * @param id the id of the {@link Plugin} + * @return {@code true} if loaded + */ + boolean isLoaded(@NonNull String id); + + /** + * Adds the specified {@code path} to the plugin classpath. + * + * @param plugin the plugin + * @param path the path to the JAR you want to inject into the classpath + * @throws UnsupportedOperationException if the operation is not applicable to this plugin + */ + void addToClasspath(@NonNull Object plugin, @NonNull Path path); +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java b/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java new file mode 100644 index 000000000..aa120d94e --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java @@ -0,0 +1,18 @@ +package com.velocitypowered.api.plugin.annotation; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation requests that Velocity inject a {@link java.nio.file.Path} instance with a plugin-specific data + * directory. + */ +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@BindingAnnotation +public @interface DataDirectory { +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java new file mode 100644 index 000000000..eb792b9a8 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java @@ -0,0 +1,79 @@ +package com.velocitypowered.api.plugin.meta; + +import javax.annotation.Nullable; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.emptyToNull; + +/** + * Represents a dependency on another plugin. + */ +public final class PluginDependency { + private final String id; + @Nullable private final String version; + + private final boolean optional; + + public PluginDependency(String id, @Nullable String version, boolean optional) { + this.id = checkNotNull(id, "id"); + checkArgument(!id.isEmpty(), "id cannot be empty"); + this.version = emptyToNull(version); + this.optional = optional; + } + + /** + * Returns the plugin ID of this {@link PluginDependency} + * + * @return the plugin ID + */ + public String getId() { + return id; + } + + /** + * Returns the version this {@link PluginDependency} should match. + * + * @return the plugin version, or {@code null} if unspecified + */ + @Nullable + public String getVersion() { + return version; + } + + /** + * Returns whether the dependency is optional for the plugin to work + * correctly. + * + * @return true if dependency is optional + */ + public boolean isOptional() { + return optional; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PluginDependency that = (PluginDependency) o; + return optional == that.optional && + Objects.equals(id, that.id) && + Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(id, version, optional); + } + + @Override + public String toString() { + return "PluginDependency{" + + "id='" + id + '\'' + + ", version='" + version + '\'' + + ", optional=" + optional + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java b/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java index 5283ef422..e4507d752 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java @@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.server.ServerInfo; import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -15,14 +16,14 @@ public interface ConnectionRequestBuilder { * Returns the server that this connection request represents. * @return the server this request will connect to */ - ServerInfo getServer(); + @NonNull ServerInfo getServer(); /** * Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user * has logged on. No messages will be communicated to the client: the user is responsible for all error handling. * @return a {@link CompletableFuture} representing the status of this connection */ - CompletableFuture connect(); + @NonNull 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/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java index 1385b594b..18bd0b421 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.proxy; import java.net.InetSocketAddress; +import java.util.Optional; /** * Represents a connection to the proxy. There is no guarantee that the connection has been fully initialized. @@ -12,6 +13,12 @@ public interface InboundConnection { */ InetSocketAddress getRemoteAddress(); + /** + * Returns the hostname that the user entered into the client, if applicable. + * @return the hostname from the client + */ + Optional getVirtualHost(); + /** * Determine whether or not the player remains online. * @return whether or not the player active 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 1e41576a6..86978ab57 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,18 +1,18 @@ package com.velocitypowered.api.proxy; -import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.api.util.MessagePosition; import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; -import javax.annotation.Nonnull; import java.util.Optional; import java.util.UUID; /** * Represents a player who is connected to the proxy. */ -public interface Player extends CommandInvoker, InboundConnection { +public interface Player extends CommandSource, InboundConnection { /** * Returns the player's current username. * @return the username @@ -35,7 +35,7 @@ public interface Player extends CommandInvoker, InboundConnection { * Sends a chat message to the player's client. * @param component the chat message to send */ - default void sendMessage(@Nonnull Component component) { + default void sendMessage(@NonNull Component component) { sendMessage(component, MessagePosition.CHAT); } @@ -44,12 +44,12 @@ public interface Player extends CommandInvoker, InboundConnection { * @param component the chat message to send * @param position the position for the message */ - void sendMessage(@Nonnull Component component, @Nonnull MessagePosition position); + void sendMessage(@NonNull Component component, @NonNull MessagePosition position); /** * Creates a new connection request so that the player can connect to another server. * @param info the server to connect to * @return a new connection request */ - ConnectionRequestBuilder createConnectionRequest(@Nonnull ServerInfo info); + ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); } 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 1dbb4b580..b35fe127c 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -1,6 +1,10 @@ package com.velocitypowered.api.proxy; -import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.server.ServerInfo; import javax.annotation.Nonnull; @@ -9,7 +13,7 @@ import java.util.Optional; import java.util.UUID; /** - * Represents a Minecraft proxy server that follows the Velocity API. + * Represents a Minecraft proxy server that is compatible with the Velocity API. */ public interface ProxyServer { /** @@ -65,10 +69,36 @@ public interface ProxyServer { void unregisterServer(@Nonnull ServerInfo server); /** - * Returns an instance of {@link CommandInvoker} that can be used to determine if the command is being invoked by + * Returns an instance of {@link CommandSource} that can be used to determine if the command is being invoked by * the console or a console-like executor. Plugins that execute commands are strongly urged to implement their own - * {@link CommandInvoker} instead of using the console invoker. + * {@link CommandSource} instead of using the console invoker. * @return the console command invoker */ - CommandInvoker getConsoleCommandInvoker(); + CommandSource getConsoleCommandSource(); + + /** + * Gets the {@link PluginManager} instance. + * + * @return the plugin manager instance + */ + PluginManager getPluginManager(); + + /** + * Gets the {@link EventManager} instance. + * + * @return the event manager instance + */ + EventManager getEventManager(); + + /** + * Gets the {@link CommandManager} instance. + * @return the command manager + */ + CommandManager getCommandManager(); + + /** + * Gets the {@link Scheduler} instance. + * @return the scheduler instance + */ + Scheduler getScheduler(); } diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java b/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java new file mode 100644 index 000000000..58596c684 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java @@ -0,0 +1,12 @@ +package com.velocitypowered.api.scheduler; + +/** + * Represents a task that is scheduled to run on the proxy. + */ +public interface ScheduledTask { + Object plugin(); + + TaskStatus status(); + + void cancel(); +} diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java new file mode 100644 index 000000000..5565a9e79 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java @@ -0,0 +1,22 @@ +package com.velocitypowered.api.scheduler; + +import java.util.concurrent.TimeUnit; + +/** + * Represents a scheduler to execute tasks on the proxy. + */ +public interface Scheduler { + TaskBuilder buildTask(Object plugin, Runnable runnable); + + interface TaskBuilder { + TaskBuilder delay(int time, TimeUnit unit); + + TaskBuilder repeat(int time, TimeUnit unit); + + TaskBuilder clearDelay(); + + TaskBuilder clearRepeat(); + + ScheduledTask schedule(); + } +} diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java b/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java new file mode 100644 index 000000000..b5830fa51 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java @@ -0,0 +1,7 @@ +package com.velocitypowered.api.scheduler; + +public enum TaskStatus { + SCHEDULED, + CANCELLED, + FINISHED +} diff --git a/api/src/main/java/com/velocitypowered/api/server/Favicon.java b/api/src/main/java/com/velocitypowered/api/server/Favicon.java index ceeaf517c..a25e5bea8 100644 --- a/api/src/main/java/com/velocitypowered/api/server/Favicon.java +++ b/api/src/main/java/com/velocitypowered/api/server/Favicon.java @@ -1,8 +1,8 @@ package com.velocitypowered.api.server; import com.google.common.base.Preconditions; +import org.checkerframework.checker.nullness.qual.NonNull; -import javax.annotation.Nonnull; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; @@ -25,7 +25,7 @@ public final class Favicon { * of functions. * @param base64Url the url for use with this favicon */ - public Favicon(@Nonnull String base64Url) { + public Favicon(@NonNull String base64Url) { this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url"); } @@ -62,7 +62,7 @@ public final class Favicon { * @param image the image to use for the favicon * @return the created {@link Favicon} instance */ - public static Favicon create(@Nonnull BufferedImage image) { + public static Favicon create(@NonNull BufferedImage image) { Preconditions.checkNotNull(image, "image"); Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, "Image does not have" + " 64x64 dimensions (found %sx%s)", image.getWidth(), image.getHeight()); @@ -79,8 +79,9 @@ public final class Favicon { * Creates a new {@code Favicon} by reading the image from the specified {@code path}. * @param path the path to the image to create a favicon for * @return the created {@link Favicon} instance + * @throws IOException if the file could not be read from the path */ - public static Favicon create(@Nonnull Path path) throws IOException { + public static Favicon create(@NonNull Path path) throws IOException { try (InputStream stream = Files.newInputStream(path)) { return create(ImageIO.read(stream)); } diff --git a/api/src/main/java/com/velocitypowered/api/server/ServerInfo.java b/api/src/main/java/com/velocitypowered/api/server/ServerInfo.java index 8bab632d0..ef83511f1 100644 --- a/api/src/main/java/com/velocitypowered/api/server/ServerInfo.java +++ b/api/src/main/java/com/velocitypowered/api/server/ServerInfo.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.server; import com.google.common.base.Preconditions; +import org.checkerframework.checker.nullness.qual.NonNull; import java.net.InetSocketAddress; import java.util.Objects; @@ -9,24 +10,24 @@ import java.util.Objects; * ServerInfo represents a server that a player can connect to. This object is immutable and safe for concurrent access. */ public final class ServerInfo { - private final String name; - private final InetSocketAddress address; + private final @NonNull String name; + private final @NonNull InetSocketAddress address; /** * Creates a new ServerInfo object. * @param name the name for the server * @param address the address of the server to connect to */ - public ServerInfo(String name, InetSocketAddress address) { + public ServerInfo(@NonNull String name, @NonNull InetSocketAddress address) { this.name = Preconditions.checkNotNull(name, "name"); this.address = Preconditions.checkNotNull(address, "address"); } - public final String getName() { + public final @NonNull String getName() { return name; } - public final InetSocketAddress getAddress() { + public final @NonNull InetSocketAddress getAddress() { return address; } diff --git a/api/src/main/java/com/velocitypowered/api/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/server/ServerPing.java new file mode 100644 index 000000000..f7842b412 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/server/ServerPing.java @@ -0,0 +1,240 @@ +package com.velocitypowered.api.server; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; + +/** + * Represents a 1.7 and above server list ping response. This class is immutable. + */ +public class ServerPing { + private final Version version; + private final Players players; + private final Component description; + private final @Nullable Favicon favicon; + + public ServerPing(@NonNull Version version, @NonNull Players players, @NonNull Component description, @Nullable Favicon favicon) { + this.version = Preconditions.checkNotNull(version, "version"); + this.players = Preconditions.checkNotNull(players, "players"); + this.description = Preconditions.checkNotNull(description, "description"); + this.favicon = favicon; + } + + public Version getVersion() { + return version; + } + + public Players getPlayers() { + return players; + } + + public Component getDescription() { + return description; + } + + public Optional getFavicon() { + return Optional.ofNullable(favicon); + } + + @Override + public String toString() { + return "ServerPing{" + + "version=" + version + + ", players=" + players + + ", description=" + description + + ", favicon='" + favicon + '\'' + + '}'; + } + + public Builder asBuilder() { + Builder builder = new Builder(); + builder.version = version; + builder.onlinePlayers = players.online; + builder.maximumPlayers = players.max; + builder.samplePlayers.addAll(players.sample); + builder.description = description; + builder.favicon = favicon; + return builder; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Version version; + private int onlinePlayers; + private int maximumPlayers; + private final List samplePlayers = new ArrayList<>(); + private Component description; + private Favicon favicon; + + private Builder() { + + } + + public Builder version(Version version) { + this.version = Preconditions.checkNotNull(version, "version"); + return this; + } + + public Builder onlinePlayers(int onlinePlayers) { + this.onlinePlayers = onlinePlayers; + return this; + } + + public Builder maximumPlayers(int maximumPlayers) { + this.maximumPlayers = maximumPlayers; + return this; + } + + public Builder samplePlayers(SamplePlayer... players) { + this.samplePlayers.addAll(Arrays.asList(players)); + return this; + } + + public Builder clearSamplePlayers() { + this.samplePlayers.clear(); + return this; + } + + public Builder description(Component description) { + this.description = Preconditions.checkNotNull(description, "description"); + return this; + } + + public Builder favicon(Favicon favicon) { + this.favicon = Preconditions.checkNotNull(favicon, "favicon"); + return this; + } + + public ServerPing build() { + return new ServerPing(version, new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon); + } + + public Version getVersion() { + return version; + } + + public int getOnlinePlayers() { + return onlinePlayers; + } + + public int getMaximumPlayers() { + return maximumPlayers; + } + + public List getSamplePlayers() { + return samplePlayers; + } + + public Component getDescription() { + return description; + } + + public Favicon getFavicon() { + return favicon; + } + + @Override + public String toString() { + return "Builder{" + + "version=" + version + + ", onlinePlayers=" + onlinePlayers + + ", maximumPlayers=" + maximumPlayers + + ", samplePlayers=" + samplePlayers + + ", description=" + description + + ", favicon=" + favicon + + '}'; + } + } + + public static class Version { + private final int protocol; + private final String name; + + public Version(int protocol, String name) { + this.protocol = protocol; + this.name = name; + } + + public int getProtocol() { + return protocol; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "Version{" + + "protocol=" + protocol + + ", name='" + name + '\'' + + '}'; + } + } + + public static class Players { + private final int online; + private final int max; + private final List sample; + + public Players(int online, int max, List sample) { + this.online = online; + this.max = max; + this.sample = ImmutableList.copyOf(sample); + } + + public int getOnline() { + return online; + } + + public int getMax() { + return max; + } + + public List getSample() { + return sample; + } + + @Override + public String toString() { + return "Players{" + + "online=" + online + + ", max=" + max + + ", sample=" + sample + + '}'; + } + } + + public static class SamplePlayer { + private final String name; + private final UUID id; + + public SamplePlayer(String name, UUID id) { + this.name = name; + this.id = id; + } + + public String getName() { + return name; + } + + public UUID getId() { + return id; + } + + @Override + public String toString() { + return "SamplePlayer{" + + "name='" + name + '\'' + + ", id=" + id + + '}'; + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/data/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java similarity index 83% rename from proxy/src/main/java/com/velocitypowered/proxy/data/GameProfile.java rename to api/src/main/java/com/velocitypowered/api/util/GameProfile.java index 6c6620c8e..f77b991f3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/data/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -1,8 +1,8 @@ -package com.velocitypowered.proxy.data; +package com.velocitypowered.api.util; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.velocitypowered.proxy.util.UuidUtils; +import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; import java.util.UUID; @@ -12,7 +12,7 @@ public class GameProfile { private final String name; private final List properties; - public GameProfile(String id, String name, List properties) { + public GameProfile(@NonNull String id, @NonNull String name, @NonNull List properties) { this.id = id; this.name = name; this.properties = ImmutableList.copyOf(properties); @@ -34,7 +34,7 @@ public class GameProfile { return ImmutableList.copyOf(properties); } - public static GameProfile forOfflinePlayer(String username) { + public static GameProfile forOfflinePlayer(@NonNull String username) { Preconditions.checkNotNull(username, "username"); String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username)); return new GameProfile(id, username, ImmutableList.of()); @@ -54,7 +54,7 @@ public class GameProfile { private final String value; private final String signature; - public Property(String name, String value, String signature) { + public Property(@NonNull String name, @NonNull String value, @NonNull String signature) { this.name = name; this.value = value; this.signature = signature; diff --git a/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java b/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java index 31703fe81..a21b97ffd 100644 --- a/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java +++ b/api/src/main/java/com/velocitypowered/api/util/LegacyChatColorUtils.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; +import org.checkerframework.checker.nullness.qual.NonNull; import java.util.regex.Pattern; @@ -25,7 +26,7 @@ public class LegacyChatColorUtils { * @param text the text to translate * @return the translated text */ - public static String translate(char originalChar, String text) { + public static String translate(char originalChar, @NonNull String text) { Preconditions.checkNotNull(text, "text"); char[] textChars = text.toCharArray(); int foundSectionIdx = -1; @@ -58,7 +59,7 @@ public class LegacyChatColorUtils { * @param text the text to remove color codes from * @return a new String without Minecraft color codes */ - public static String removeFormatting(String text) { + public static String removeFormatting(@NonNull String text) { Preconditions.checkNotNull(text, "text"); return CHAT_COLOR_MATCHER.matcher(text).replaceAll(""); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java b/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java similarity index 63% rename from proxy/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java rename to api/src/main/java/com/velocitypowered/api/util/UuidUtils.java index d42810ab2..3cff823cd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java +++ b/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java @@ -1,15 +1,18 @@ -package com.velocitypowered.proxy.util; +package com.velocitypowered.api.util; import com.google.common.base.Preconditions; +import org.checkerframework.checker.nullness.qual.NonNull; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.UUID; -public enum UuidUtils { - ; +public class UuidUtils { + private UuidUtils() { + throw new AssertionError(); + } - public static UUID fromUndashed(final String string) { + public static @NonNull UUID fromUndashed(final @NonNull String string) { Objects.requireNonNull(string, "string"); Preconditions.checkArgument(string.length() == 32, "Length is incorrect"); return new UUID( @@ -18,12 +21,12 @@ public enum UuidUtils { ); } - public static String toUndashed(final UUID uuid) { + public static @NonNull String toUndashed(final @NonNull UUID uuid) { Preconditions.checkNotNull(uuid, "uuid"); return Long.toUnsignedString(uuid.getMostSignificantBits(), 16) + Long.toUnsignedString(uuid.getLeastSignificantBits(), 16); } - public static UUID generateOfflinePlayerUuid(String username) { + public static @NonNull UUID generateOfflinePlayerUuid(@NonNull String username) { return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); } } diff --git a/build.gradle b/build.gradle index 42320b400..0b46cc4c2 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ allprojects { ext { // dependency versions junitVersion = '5.3.0-M1' + slf4jVersion = '1.7.25' log4jVersion = '2.11.0' nettyVersion = '4.1.28.Final' guavaVersion = '25.1-jre' @@ -30,4 +31,4 @@ allprojects { junitXml.enabled = true } } -} \ No newline at end of file +} diff --git a/proxy/build.gradle b/proxy/build.gradle index af559e9ec..547faed22 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -32,12 +32,14 @@ dependencies { compile "org.apache.logging.log4j:log4j-api:${log4jVersion}" compile "org.apache.logging.log4j:log4j-core:${log4jVersion}" + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}" 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 compile 'it.unimi.dsi:fastutil:8.2.1' + compile 'net.kyori:event-method-asm:3.0.0' testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 9102ce3fe..73a15ae9d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -4,11 +4,15 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.velocitypowered.api.command.CommandInvoker; +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.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.server.Favicon; -import com.velocitypowered.natives.util.Natives; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.network.ConnectionManager; import com.velocitypowered.proxy.command.ServerCommand; import com.velocitypowered.proxy.command.ShutdownCommand; @@ -16,9 +20,12 @@ import com.velocitypowered.proxy.command.VelocityCommand; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; -import com.velocitypowered.api.server.ServerInfo; -import com.velocitypowered.proxy.command.CommandManager; +import com.velocitypowered.proxy.command.VelocityCommandManager; +import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.protocol.util.FaviconSerializer; +import com.velocitypowered.proxy.plugin.VelocityPluginManager; +import com.velocitypowered.proxy.scheduler.Sleeper; +import com.velocitypowered.proxy.scheduler.VelocityScheduler; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.Ratelimiter; @@ -40,6 +47,7 @@ import java.nio.file.Paths; import java.security.KeyPair; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; public class VelocityServer implements ProxyServer { @@ -55,13 +63,14 @@ public class VelocityServer implements ProxyServer { private NettyHttpClient httpClient; private KeyPair serverKeyPair; private final ServerMap servers = new ServerMap(); - private final CommandManager commandManager = new CommandManager(); + private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private boolean shutdown = false; + private final VelocityPluginManager pluginManager = new VelocityPluginManager(this); private final Map connectionsByUuid = new ConcurrentHashMap<>(); private final Map connectionsByName = new ConcurrentHashMap<>(); - private final CommandInvoker consoleCommandInvoker = new CommandInvoker() { + private final CommandSource consoleCommandSource = new CommandSource() { @Override public void sendMessage(@Nonnull Component component) { logger.info(ComponentSerializers.LEGACY.serialize(component)); @@ -73,11 +82,13 @@ public class VelocityServer implements ProxyServer { } }; private Ratelimiter ipAttemptLimiter; + private VelocityEventManager eventManager; + private VelocityScheduler scheduler; private VelocityServer() { - commandManager.registerCommand("velocity", new VelocityCommand()); - commandManager.registerCommand("server", new ServerCommand()); - commandManager.registerCommand("shutdown", new ShutdownCommand()); + commandManager.register(new VelocityCommand(), "velocity"); + commandManager.register(new ServerCommand(), "server"); + commandManager.register(new ShutdownCommand(), "shutdown"); } public static VelocityServer getServer() { @@ -92,7 +103,8 @@ public class VelocityServer implements ProxyServer { return configuration; } - public CommandManager getCommandManager() { + @Override + public VelocityCommandManager getCommandManager() { return commandManager; } @@ -121,10 +133,21 @@ public class VelocityServer implements ProxyServer { } serverKeyPair = EncryptionUtils.createRsaKeyPair(1024); - ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit()); - httpClient = new NettyHttpClient(this); + eventManager = new VelocityEventManager(pluginManager); + scheduler = new VelocityScheduler(pluginManager, Sleeper.SYSTEM); + loadPlugins(); + + // Post the first event + pluginManager.getPlugins().forEach(container -> { + container.getInstance().ifPresent(plugin -> eventManager.register(plugin, plugin)); + }); + try { + eventManager.fire(new ProxyInitializeEvent()).get(); + } catch (InterruptedException | ExecutionException e) { + // Ignore, we don't care. + } this.cm.bind(configuration.getBind()); @@ -133,6 +156,29 @@ public class VelocityServer implements ProxyServer { } } + private void loadPlugins() { + logger.info("Loading plugins..."); + + try { + Path pluginPath = Paths.get("plugins"); + + if (Files.notExists(pluginPath)) { + Files.createDirectory(pluginPath); + } else { + if (!Files.isDirectory(pluginPath)) { + logger.warn("Plugin location {} is not a directory, continuing without loading plugins", pluginPath); + return; + } + + pluginManager.loadPlugins(pluginPath); + } + } catch (Exception e) { + logger.error("Couldn't load plugins", e); + } + + logger.info("Loaded {} plugins", pluginManager.getPlugins().size()); + } + public ServerMap getServers() { return servers; } @@ -154,6 +200,14 @@ public class VelocityServer implements ProxyServer { } this.cm.shutdown(); + + eventManager.fire(new ProxyShutdownEvent()); + try { + eventManager.shutdown(); + } catch (InterruptedException e) { + logger.error("Your plugins took over 10 seconds to shut down."); + } + shutdown = true; } @@ -226,7 +280,22 @@ public class VelocityServer implements ProxyServer { } @Override - public CommandInvoker getConsoleCommandInvoker() { - return consoleCommandInvoker; + public CommandSource getConsoleCommandSource() { + return consoleCommandSource; + } + + @Override + public PluginManager getPluginManager() { + return pluginManager; + } + + @Override + public EventManager getEventManager() { + return eventManager; + } + + @Override + public VelocityScheduler getScheduler() { + return scheduler; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java deleted file mode 100644 index a9871a08d..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.velocitypowered.proxy.command; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.command.CommandExecutor; -import com.velocitypowered.api.command.CommandInvoker; - -import java.util.*; -import java.util.stream.Collectors; - -public class CommandManager { - private final Map executors = new HashMap<>(); - - public void registerCommand(String name, CommandExecutor executor) { - Preconditions.checkNotNull(name, "name"); - Preconditions.checkNotNull(executor, "executor"); - this.executors.put(name, executor); - } - - public void unregisterCommand(String name) { - Preconditions.checkNotNull(name, "name"); - this.executors.remove(name); - } - - public boolean execute(CommandInvoker invoker, String cmdLine) { - Preconditions.checkNotNull(invoker, "invoker"); - Preconditions.checkNotNull(cmdLine, "cmdLine"); - - String[] split = cmdLine.split(" ", -1); - if (split.length == 0) { - return false; - } - - String command = split[0]; - String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); - CommandExecutor executor = executors.get(command); - if (executor == null) { - return false; - } - - try { - executor.execute(invoker, actualArgs); - return true; - } catch (Exception e) { - throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + invoker, e); - } - } - - public Optional> offerSuggestions(CommandInvoker invoker, String cmdLine) { - Preconditions.checkNotNull(invoker, "invoker"); - Preconditions.checkNotNull(cmdLine, "cmdLine"); - - String[] split = cmdLine.split(" ", -1); - if (split.length == 0) { - return Optional.empty(); - } - - String command = split[0]; - if (split.length == 1) { - return Optional.of(executors.keySet().stream() - .filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length())) - .collect(Collectors.toList())); - } - - String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); - CommandExecutor executor = executors.get(command); - if (executor == null) { - return Optional.empty(); - } - - try { - return Optional.of(executor.suggest(invoker, actualArgs)); - } catch (Exception e) { - throw new RuntimeException("Unable to invoke suggestions for command " + command + " for " + invoker, e); - } - } -} 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 16a05d021..ee68dbb1d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -1,8 +1,8 @@ package com.velocitypowered.proxy.command; import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.command.CommandExecutor; -import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.VelocityServer; @@ -14,15 +14,15 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -public class ServerCommand implements CommandExecutor { +public class ServerCommand implements Command { @Override - public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) { - if (!(invoker instanceof Player)) { - invoker.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED)); + public void execute(@Nonnull CommandSource source, @Nonnull String[] args) { + if (!(source instanceof Player)) { + source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED)); return; } - Player player = (Player) invoker; + Player player = (Player) source; if (args.length == 1) { // Trying to connect to a server. String serverName = args[0]; @@ -42,7 +42,7 @@ public class ServerCommand implements CommandExecutor { } @Override - public List suggest(@Nonnull CommandInvoker invoker, @Nonnull String[] currentArgs) { + public List suggest(@Nonnull CommandSource source, @Nonnull String[] currentArgs) { if (currentArgs.length == 0) { return VelocityServer.getServer().getAllServers().stream() .map(ServerInfo::getName) 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 302a49248..563ecd1fb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java @@ -1,18 +1,18 @@ package com.velocitypowered.proxy.command; -import com.velocitypowered.api.command.CommandExecutor; -import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.proxy.VelocityServer; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import javax.annotation.Nonnull; -public class ShutdownCommand implements CommandExecutor { +public class ShutdownCommand implements Command { @Override - public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) { - if (invoker != VelocityServer.getServer().getConsoleCommandInvoker()) { - invoker.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED)); + public void execute(@Nonnull CommandSource source, @Nonnull String[] args) { + if (source != VelocityServer.getServer().getConsoleCommandSource()) { + source.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED)); return; } VelocityServer.getServer().shutdown(); 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 272fe4777..fd1ae6b13 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.command; -import com.velocitypowered.api.command.CommandExecutor; -import com.velocitypowered.api.command.CommandInvoker; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.proxy.VelocityServer; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; @@ -9,9 +9,9 @@ import net.kyori.text.format.TextColor; import javax.annotation.Nonnull; -public class VelocityCommand implements CommandExecutor { +public class VelocityCommand implements Command { @Override - public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) { + public void execute(@Nonnull CommandSource source, @Nonnull String[] args) { String implVersion = VelocityServer.class.getPackage().getImplementationVersion(); TextComponent thisIsVelocity = TextComponent.builder() .content("This is ") @@ -35,8 +35,8 @@ public class VelocityCommand implements CommandExecutor { .build()) .build(); - invoker.sendMessage(thisIsVelocity); - invoker.sendMessage(velocityInfo); - invoker.sendMessage(velocityWebsite); + source.sendMessage(thisIsVelocity); + source.sendMessage(velocityInfo); + source.sendMessage(velocityWebsite); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java new file mode 100644 index 000000000..38c3759ea --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -0,0 +1,85 @@ +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 java.util.*; +import java.util.stream.Collectors; + +public class VelocityCommandManager implements CommandManager { + private final Map commands = new HashMap<>(); + + @Override + public void register(final Command command, final String... aliases) { + Preconditions.checkNotNull(aliases, "aliases"); + 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); + this.commands.put(alias.toLowerCase(Locale.ENGLISH), command); + } + } + + @Override + public void unregister(final String alias) { + Preconditions.checkNotNull(alias, "name"); + this.commands.remove(alias.toLowerCase(Locale.ENGLISH)); + } + + @Override + public boolean execute(CommandSource source, String cmdLine) { + Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); + + String[] split = cmdLine.split(" ", -1); + if (split.length == 0) { + return false; + } + + String alias = split[0]; + String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); + Command command = commands.get(alias.toLowerCase(Locale.ENGLISH)); + if (command == null) { + return false; + } + + try { + command.execute(source, actualArgs); + return true; + } catch (Exception e) { + throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e); + } + } + + public Optional> offerSuggestions(CommandSource source, String cmdLine) { + Preconditions.checkNotNull(source, "source"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); + + String[] split = cmdLine.split(" ", -1); + if (split.length == 0) { + return Optional.empty(); + } + + String command = split[0]; + if (split.length == 1) { + return Optional.of(commands.keySet().stream() + .filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length())) + .collect(Collectors.toList())); + } + + String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); + Command executor = commands.get(command); + if (executor == null) { + return Optional.empty(); + } + + try { + return Optional.of(executor.suggest(source, actualArgs)); + } catch (Exception e) { + throw new RuntimeException("Unable to invoke suggestions for command " + command + " for " + source, e); + } + } +} 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 fadabf151..5d3797fec 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,5 +1,7 @@ package com.velocitypowered.proxy.connection.backend; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -16,7 +18,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } @Override - public void handle(MinecraftPacket packet) { + public void activated() { + VelocityServer.getServer().getEventManager().fireAndForget(new ServerConnectedEvent(connection.getProxyPlayer(), + connection.getServerInfo())); + } + + @Override + public void handle(MinecraftPacket packet) { //Not handleable packets: Chat, TabCompleteResponse, Respawn, Scoreboard* if (!connection.getProxyPlayer().isActive()) { // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" 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 24145a4f2..4cd1cd9c2 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 @@ -7,7 +7,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; -import com.velocitypowered.proxy.data.GameProfile; +import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; 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 549c8fb46..32344337d 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,5 +1,6 @@ package com.velocitypowered.proxy.connection.client; +import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -82,19 +83,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { response.setTransactionId(req.getTransactionId()); response.setStart(lastSpace); response.setLength(req.getCommand().length() - lastSpace); + for (String s : offers.get()) { response.getOffers().add(new TabCompleteResponse.Offer(s, null)); } + player.getConnection().write(response); - return; + } else { + player.getConnectedServer().getMinecraftConnection().write(packet); } } catch (Exception e) { logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e); - TabCompleteResponse response = new TabCompleteResponse(); - response.setTransactionId(req.getTransactionId()); - player.getConnection().write(response); - return; } + return; } } @@ -116,6 +117,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public void disconnected() { player.teardown(); + VelocityServer.getServer().getEventManager().fireAndForget(new DisconnectEvent(player)); } @Override 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 bf4f05916..ac4220624 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 @@ -2,6 +2,9 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; import com.google.gson.JsonObject; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.permission.PermissionProvider; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.proxy.Player; @@ -9,7 +12,7 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; -import com.velocitypowered.proxy.data.GameProfile; +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.ServerConnection; @@ -25,6 +28,7 @@ import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.PlainComponentSerializer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.NonNull; import javax.annotation.Nonnull; import java.net.InetSocketAddress; @@ -35,19 +39,23 @@ import java.util.concurrent.CompletableFuture; public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private static final PlainComponentSerializer PASS_THRU_TRANSLATE = new PlainComponentSerializer((c) -> "", TranslatableComponent::key); + public static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class); private final GameProfile profile; private final MinecraftConnection connection; + private final InetSocketAddress virtualHost; + private PermissionFunction permissionFunction = null; private int tryIndex = 0; private ServerConnection connectedServer; private ClientSettings clientSettings; private ServerConnection connectionInFlight; - public ConnectedPlayer(GameProfile profile, MinecraftConnection connection) { + public ConnectedPlayer(GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) { this.profile = profile; this.connection = connection; + this.virtualHost = virtualHost; } @Override @@ -78,6 +86,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return (InetSocketAddress) connection.getChannel().remoteAddress(); } + @Override + public Optional getVirtualHost() { + return Optional.ofNullable(virtualHost); + } + + public void setPermissionFunction(PermissionFunction permissionFunction) { + this.permissionFunction = permissionFunction; + } + @Override public boolean isActive() { return connection.getChannel().isActive(); @@ -89,7 +106,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public void sendMessage(@Nonnull Component component, @Nonnull MessagePosition position) { + public void sendMessage(@NonNull Component component, @NonNull MessagePosition position) { Preconditions.checkNotNull(component, "component"); Preconditions.checkNotNull(position, "position"); @@ -112,7 +129,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public ConnectionRequestBuilder createConnectionRequest(@Nonnull ServerInfo info) { + public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) { return new ConnectionRequestBuilderImpl(info); } @@ -191,8 +208,17 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } // Otherwise, initiate the connection. - ServerConnection connection = new ServerConnection(request.getServer(), this, VelocityServer.getServer()); - return connection.connect(); + ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer())); + return VelocityServer.getServer().getEventManager().fire(event) + .thenCompose((newEvent) -> { + if (!newEvent.getResult().isAllowed()) { + return CompletableFuture.completedFuture( + ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED) + ); + } + + return new ServerConnection(newEvent.getResult().getInfo().get(), this, VelocityServer.getServer()).connect(); + }); } public void setConnectedServer(ServerConnection serverConnection) { @@ -223,13 +249,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { @Override public boolean hasPermission(@Nonnull String permission) { - return false; // TODO: Implement permissions. + return permissionFunction.getPermissionSetting(permission).asBoolean(); } private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final ServerInfo info; - public ConnectionRequestBuilderImpl(ServerInfo info) { + ConnectionRequestBuilderImpl(ServerInfo info) { this.info = Preconditions.checkNotNull(info, "info"); } 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 a603e7c8a..8b089a6ee 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 @@ -1,12 +1,15 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; +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.proxy.VelocityServer; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.data.ServerPing; +import com.velocitypowered.api.server.ServerPing; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; @@ -17,6 +20,7 @@ import net.kyori.text.format.TextColor; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Optional; public class HandshakeSessionHandler implements MinecraftSessionHandler { private final MinecraftConnection connection; @@ -37,12 +41,14 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName()); } + InitialInboundConnection ic = new InitialInboundConnection(connection, (Handshake) packet); + Handshake handshake = (Handshake) packet; switch (handshake.getNextStatus()) { case StateRegistry.STATUS_ID: connection.setState(StateRegistry.STATUS); connection.setProtocolVersion(handshake.getProtocolVersion()); - connection.setSessionHandler(new StatusSessionHandler(connection)); + connection.setSessionHandler(new StatusSessionHandler(connection, ic)); break; case StateRegistry.LOGIN_ID: connection.setState(StateRegistry.LOGIN); @@ -56,7 +62,8 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { connection.closeWith(Disconnect.create(TextComponent.of("You are logging in too fast, try again later."))); return; } - connection.setSessionHandler(new LoginSessionHandler(connection)); + VelocityServer.getServer().getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic)); + connection.setSessionHandler(new LoginSessionHandler(connection, ic)); } break; default: @@ -69,21 +76,25 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); ServerPing ping = new ServerPing( new ServerPing.Version(ProtocolConstants.MAXIMUM_GENERIC_VERSION, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), - new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()), + new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()), configuration.getMotdComponent(), null ); - // The disconnect packet is the same as the server response one. - connection.closeWith(LegacyDisconnect.fromPingResponse(LegacyPingResponse.from(ping))); + ProxyPingEvent event = new ProxyPingEvent(new LegacyInboundConnection(connection), ping); + VelocityServer.getServer().getEventManager().fire(event) + .thenRunAsync(() -> { + // The disconnect packet is the same as the server response one. + connection.closeWith(LegacyDisconnect.fromPingResponse(LegacyPingResponse.from(event.getPing()))); + }, connection.getChannel().eventLoop()); } else if (packet instanceof LegacyHandshake) { connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED))); } } - private static class InitialInboundConnection implements InboundConnection { + private static class LegacyInboundConnection implements InboundConnection { private final MinecraftConnection connection; - private InitialInboundConnection(MinecraftConnection connection) { + private LegacyInboundConnection(MinecraftConnection connection) { this.connection = connection; } @@ -92,14 +103,19 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { return (InetSocketAddress) connection.getChannel().remoteAddress(); } + @Override + public Optional getVirtualHost() { + return Optional.empty(); + } + @Override public boolean isActive() { - return connection.getChannel().isActive(); + return !connection.isClosed(); } @Override public int getProtocolVersion() { - return connection.getProtocolVersion(); + return 0; } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java new file mode 100644 index 000000000..6c4ba9069 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java @@ -0,0 +1,38 @@ +package com.velocitypowered.proxy.connection.client; + +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.protocol.packet.Handshake; + +import java.net.InetSocketAddress; +import java.util.Optional; + +class InitialInboundConnection implements InboundConnection { + private final MinecraftConnection connection; + private final Handshake handshake; + + InitialInboundConnection(MinecraftConnection connection, Handshake handshake) { + this.connection = connection; + this.handshake = handshake; + } + + @Override + public InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) connection.getChannel().remoteAddress(); + } + + @Override + public Optional getVirtualHost() { + return Optional.of(InetSocketAddress.createUnresolved(handshake.getServerAddress(), handshake.getPort())); + } + + @Override + public boolean isActive() { + return connection.getChannel().isActive(); + } + + @Override + public int getProtocolVersion() { + return connection.getProtocolVersion(); + } +} 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 594b8ce26..c912ba7f0 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 @@ -1,8 +1,13 @@ 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.PreLoginEvent; +import com.velocitypowered.api.event.permission.PermissionsSetupEvent; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.connection.VelocityConstants; -import com.velocitypowered.proxy.data.GameProfile; +import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; @@ -10,7 +15,6 @@ 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.api.server.ServerInfo; import com.velocitypowered.proxy.util.EncryptionUtils; import io.netty.buffer.Unpooled; import net.kyori.text.TextComponent; @@ -33,12 +37,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; private final MinecraftConnection inbound; + private final InboundConnection apiInbound; private ServerLogin login; private byte[] verify; private int playerInfoId; - public LoginSessionHandler(MinecraftConnection inbound) { + public LoginSessionHandler(MinecraftConnection inbound, InboundConnection apiInbound) { this.inbound = Preconditions.checkNotNull(inbound, "inbound"); + this.apiInbound = Preconditions.checkNotNull(apiInbound, "apiInbound"); } @Override @@ -53,7 +59,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { )); } else { // Proceed with the regular login process. - initiateLogin(); + beginPreLogin(); } } } else if (packet instanceof ServerLogin) { @@ -67,7 +73,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { message.setData(Unpooled.EMPTY_BUFFER); inbound.write(message); } else { - initiateLogin(); + beginPreLogin(); } } else if (packet instanceof EncryptionResponse) { try { @@ -97,7 +103,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class); - handleSuccessfulLogin(profile); + initializePlayer(profile); }, inbound.getChannel().eventLoop()) .exceptionally(exception -> { logger.error("Unable to enable encryption", exception); @@ -113,16 +119,26 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } } - private void initiateLogin() { - if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { - // Request encryption. - EncryptionRequest request = generateRequest(); - this.verify = Arrays.copyOf(request.getVerifyToken(), 4); - inbound.write(request); - } else { - // Offline-mode, don't try to request encryption. - handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); - } + private void beginPreLogin() { + PreLoginEvent event = new PreLoginEvent(apiInbound, login.getUsername()); + VelocityServer.getServer().getEventManager().fire(event) + .thenRunAsync(() -> { + if (!event.getResult().isAllowed()) { + // The component is guaranteed to be provided if the connection was denied. + inbound.closeWith(Disconnect.create(event.getResult().getReason().get())); + return; + } + + if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { + // Request encryption. + EncryptionRequest request = generateRequest(); + this.verify = Arrays.copyOf(request.getVerifyToken(), 4); + inbound.write(request); + } else { + // Offline-mode, don't try to request encryption. + initializePlayer(GameProfile.forOfflinePlayer(login.getUsername())); + } + }, inbound.getChannel().eventLoop()); } private EncryptionRequest generateRequest() { @@ -135,9 +151,31 @@ public class LoginSessionHandler implements MinecraftSessionHandler { return request; } - private void handleSuccessfulLogin(GameProfile profile) { + private void initializePlayer(GameProfile profile) { // Initiate a regular connection and move over to it. - ConnectedPlayer player = new ConnectedPlayer(profile, inbound); + ConnectedPlayer player = new ConnectedPlayer(profile, inbound, apiInbound.getVirtualHost().orElse(null)); + + // load permissions first + VelocityServer.getServer().getEventManager().fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) + .thenCompose(event -> { + // wait for permissions to load, then set the players permission function + player.setPermissionFunction(event.createFunction(player)); + // then call & wait for the login event + return VelocityServer.getServer().getEventManager().fire(new LoginEvent(player)); + }) + // then complete the connection + .thenAcceptAsync(event -> { + if (!event.getResult().isAllowed()) { + // The component is guaranteed to be provided if the connection was denied. + inbound.closeWith(Disconnect.create(event.getResult().getReason().get())); + return; + } + + handleProxyLogin(player); + }, inbound.getChannel().eventLoop()); + } + + private void handleProxyLogin(ConnectedPlayer player) { Optional toTry = player.getNextServerToTry(); if (!toTry.isPresent()) { player.close(TextComponent.of("No available servers", TextColor.RED)); @@ -151,8 +189,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } ServerLoginSuccess success = new ServerLoginSuccess(); - success.setUsername(profile.getName()); - success.setUuid(profile.idAsUuid()); + success.setUsername(player.getUsername()); + success.setUuid(player.getUniqueId()); inbound.write(success); inbound.setAssociation(player); 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 7d5f886bd..bef061769 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 @@ -1,6 +1,9 @@ package com.velocitypowered.proxy.connection.client; 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.proxy.VelocityServer; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -9,16 +12,18 @@ 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.proxy.data.ServerPing; +import com.velocitypowered.api.server.ServerPing; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; public class StatusSessionHandler implements MinecraftSessionHandler { private final MinecraftConnection connection; + private final InboundConnection inboundWrapper; - public StatusSessionHandler(MinecraftConnection connection) { + public StatusSessionHandler(MinecraftConnection connection, InboundConnection inboundWrapper) { this.connection = connection; + this.inboundWrapper = inboundWrapper; } @Override @@ -37,15 +42,20 @@ public class StatusSessionHandler implements MinecraftSessionHandler { // Status request int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection.getProtocolVersion() : ProtocolConstants.MAXIMUM_GENERIC_VERSION; - ServerPing ping = new ServerPing( + ServerPing initialPing = new ServerPing( new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), - new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()), + new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()), configuration.getMotdComponent(), configuration.getFavicon() ); - StatusResponse response = new StatusResponse(); - response.setStatus(VelocityServer.GSON.toJson(ping)); - connection.write(response); + + ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); + VelocityServer.getServer().getEventManager().fire(event) + .thenRunAsync(() -> { + StatusResponse response = new StatusResponse(); + response.setStatus(VelocityServer.GSON.toJson(event.getPing())); + connection.write(response); + }, connection.getChannel().eventLoop()); } @Override 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 4c46764fa..fcb1d34f9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -22,13 +22,12 @@ public final class VelocityConsole extends SimpleTerminalConsole { return super.buildReader(builder .appName("Velocity") .completer((reader, parsedLine, list) -> { - Optional> offers = server.getCommandManager().offerSuggestions(server.getConsoleCommandInvoker(), parsedLine.line()); - if (offers.isPresent()) { - for (String offer : offers.get()) { - if (offer.isEmpty()) continue; + Optional> o = server.getCommandManager().offerSuggestions(server.getConsoleCommandSource(), parsedLine.line()); + o.ifPresent(offers -> { + for (String offer : offers) { list.add(new Candidate(offer)); } - } + }); }) ); } @@ -40,8 +39,8 @@ public final class VelocityConsole extends SimpleTerminalConsole { @Override protected void runCommand(String command) { - if (!this.server.getCommandManager().execute(this.server.getConsoleCommandInvoker(), command)) { - server.getConsoleCommandInvoker().sendMessage(TextComponent.of("Command not found.", TextColor.RED)); + if (!this.server.getCommandManager().execute(this.server.getConsoleCommandSource(), command)) { + server.getConsoleCommandSource().sendMessage(TextComponent.of("Command not found.", TextColor.RED)); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java b/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java deleted file mode 100644 index a6cb2a746..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.velocitypowered.proxy.data; - -import com.velocitypowered.api.server.Favicon; -import net.kyori.text.Component; - -public class ServerPing { - private final Version version; - private final Players players; - private final Component description; - private final Favicon favicon; - - public ServerPing(Version version, Players players, Component description, Favicon favicon) { - this.version = version; - this.players = players; - this.description = description; - this.favicon = favicon; - } - - public Version getVersion() { - return version; - } - - public Players getPlayers() { - return players; - } - - public Component getDescription() { - return description; - } - - public Favicon getFavicon() { - return favicon; - } - - @Override - public String toString() { - return "ServerPing{" + - "version=" + version + - ", players=" + players + - ", description=" + description + - ", favicon='" + favicon + '\'' + - '}'; - } - - public static class Version { - private final int protocol; - private final String name; - - public Version(int protocol, String name) { - this.protocol = protocol; - this.name = name; - } - - public int getProtocol() { - return protocol; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return "Version{" + - "protocol=" + protocol + - ", name='" + name + '\'' + - '}'; - } - } - - public static class Players { - private final int online; - private final int max; - - public Players(int online, int max) { - this.online = online; - this.max = max; - } - - public int getOnline() { - return online; - } - - public int getMax() { - return max; - } - - @Override - public String toString() { - return "Players{" + - "online=" + online + - ", max=" + max + - '}'; - } - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java new file mode 100644 index 000000000..db2d2ad92 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java @@ -0,0 +1,56 @@ +package com.velocitypowered.proxy.plugin; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +public class PluginClassLoader extends URLClassLoader { + private static final Set loaders = new CopyOnWriteArraySet<>(); + + static { + ClassLoader.registerAsParallelCapable(); + } + + public PluginClassLoader(URL[] urls) { + super(urls); + loaders.add(this); + } + + public void addPath(Path path) { + try { + addURL(path.toUri().toURL()); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return loadClass0(name, resolve, true); + } + + private Class loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException { + try { + return super.loadClass(name, resolve); + } catch (ClassNotFoundException ignored) { + // Ignored: we'll try others + } + + if (checkOther) { + for (PluginClassLoader loader : loaders) { + if (loader != this) { + try { + return loader.loadClass0(name, resolve, false); + } catch (ClassNotFoundException ignored) { + // We're trying others, safe to ignore + } + } + } + } + + throw new ClassNotFoundException(name); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java new file mode 100644 index 000000000..fa828883e --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -0,0 +1,192 @@ +package com.velocitypowered.proxy.plugin; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.velocitypowered.api.event.EventHandler; +import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.proxy.util.concurrency.ThreadRecorderThreadFactory; +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.asm.ASMEventExecutorFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.reflect.Method; +import java.net.URL; +import java.util.*; +import java.util.concurrent.*; + +public class VelocityEventManager implements EventManager { + private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); + + private final ListMultimap registeredListenersByPlugin = Multimaps + .synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); + private final ListMultimap> registeredHandlersByPlugin = Multimaps + .synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); + private final VelocityEventBus bus = new VelocityEventBus( + new ASMEventExecutorFactory<>(new PluginClassLoader(new URL[0])), + new VelocityMethodScanner()); + private final ExecutorService service; + private final ThreadRecorderThreadFactory recordingThreadFactory; + private final PluginManager pluginManager; + + public VelocityEventManager(PluginManager pluginManager) { + this.pluginManager = pluginManager; + this.recordingThreadFactory = new ThreadRecorderThreadFactory(new ThreadFactoryBuilder() + .setNameFormat("Velocity Event Executor - #%d").setDaemon(true).build()); + this.service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), recordingThreadFactory); + } + + @Override + public void register(@NonNull Object plugin, @NonNull Object listener) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkNotNull(listener, "listener"); + Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded"); + registeredListenersByPlugin.put(plugin, listener); + bus.register(listener); + } + + @Override + public void register(@NonNull Object plugin, @NonNull Class eventClass, @NonNull PostOrder postOrder, @NonNull EventHandler handler) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkNotNull(eventClass, "eventClass"); + Preconditions.checkNotNull(postOrder, "postOrder"); + Preconditions.checkNotNull(handler, "listener"); + bus.register(eventClass, new KyoriToVelocityHandler<>(handler, postOrder)); + } + + @Override + public @NonNull CompletableFuture fire(@NonNull E event) { + Preconditions.checkNotNull(event, "event"); + if (!bus.hasSubscribers(event.getClass())) { + // Optimization: nobody's listening. + return CompletableFuture.completedFuture(event); + } + + Runnable runEvent = () -> { + PostResult result = bus.post(event); + if (!result.exceptions().isEmpty()) { + logger.error("Some errors occurred whilst posting event {}.", event); + int i = 0; + for (Throwable exception : result.exceptions().values()) { + logger.error("#{}: \n", i++, exception); + } + } + }; + + if (recordingThreadFactory.currentlyInFactory()) { + // Optimization: fire the event immediately, we are on the event handling thread. + runEvent.run(); + return CompletableFuture.completedFuture(event); + } + + CompletableFuture eventFuture = new CompletableFuture<>(); + service.execute(() -> { + runEvent.run(); + eventFuture.complete(event); + }); + return eventFuture; + } + + @Override + public void unregisterListeners(@NonNull Object plugin) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded"); + Collection listeners = registeredListenersByPlugin.removeAll(plugin); + listeners.forEach(bus::unregister); + Collection> handlers = registeredHandlersByPlugin.removeAll(plugin); + handlers.forEach(bus::unregister); + } + + @Override + public void unregisterListener(@NonNull Object plugin, @NonNull Object listener) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkNotNull(listener, "listener"); + Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded"); + registeredListenersByPlugin.remove(plugin, listener); + bus.unregister(listener); + } + + @Override + public void unregister(@NonNull Object plugin, @NonNull EventHandler handler) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkNotNull(handler, "listener"); + registeredHandlersByPlugin.remove(plugin, handler); + bus.unregister(handler); + } + + public void shutdown() throws InterruptedException { + service.shutdown(); + service.awaitTermination(10, TimeUnit.SECONDS); + } + + private static class VelocityEventBus extends SimpleEventBus { + private final MethodSubscriptionAdapter methodAdapter; + + VelocityEventBus(EventExecutor.@NonNull Factory factory, @NonNull MethodScanner methodScanner) { + super(Object.class); + this.methodAdapter = new SimpleMethodSubscriptionAdapter<>(this, factory, methodScanner); + } + + void register(Object listener) { + this.methodAdapter.register(listener); + } + + void unregister(Object listener) { + this.methodAdapter.unregister(listener); + } + + void unregister(EventHandler handler) { + this.unregister(s -> s instanceof KyoriToVelocityHandler && ((KyoriToVelocityHandler) s).getHandler().equals(handler)); + } + } + + private static class VelocityMethodScanner implements MethodScanner { + @Override + public boolean shouldRegister(@NonNull Object listener, @NonNull Method method) { + return method.isAnnotationPresent(Subscribe.class); + } + + @Override + public int postOrder(@NonNull Object listener, @NonNull Method method) { + return method.getAnnotation(Subscribe.class).order().ordinal(); + } + + @Override + public boolean consumeCancelledEvents(@NonNull Object listener, @NonNull Method method) { + return true; + } + } + + private static class KyoriToVelocityHandler implements EventSubscriber { + private final EventHandler handler; + private final int postOrder; + + private KyoriToVelocityHandler(EventHandler handler, PostOrder postOrder) { + this.handler = handler; + this.postOrder = postOrder.ordinal(); + } + + @Override + public void invoke(@NonNull E event) { + handler.execute(event); + } + + @Override + public int postOrder() { + return postOrder; + } + + public EventHandler getHandler() { + return handler; + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java new file mode 100644 index 000000000..85ae7fbf4 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -0,0 +1,129 @@ +package com.velocitypowered.proxy.plugin; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.plugin.loader.JavaPluginLoader; +import com.velocitypowered.proxy.plugin.util.PluginDependencyUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class VelocityPluginManager implements PluginManager { + private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class); + + private final Map plugins = new HashMap<>(); + private final Map pluginInstances = new IdentityHashMap<>(); + private final VelocityServer server; + + public VelocityPluginManager(VelocityServer server) { + this.server = checkNotNull(server, "server"); + } + + private void registerPlugin(@NonNull PluginContainer plugin) { + plugins.put(plugin.getDescription().getId(), plugin); + plugin.getInstance().ifPresent(instance -> pluginInstances.put(instance, plugin)); + } + + public void loadPlugins(@NonNull Path directory) throws IOException { + checkNotNull(directory, "directory"); + checkArgument(Files.isDirectory(directory), "provided path isn't a directory"); + + List found = new ArrayList<>(); + JavaPluginLoader loader = new JavaPluginLoader(server, directory); + + try (DirectoryStream stream = Files.newDirectoryStream(directory, p -> Files.isRegularFile(p) && p.toString().endsWith(".jar"))) { + for (Path path : stream) { + try { + found.add(loader.loadPlugin(path)); + } catch (Exception e) { + logger.error("Unable to load plugin {}", path, e); + } + } + } + + if (found.isEmpty()) { + // No plugins found + return; + } + + List sortedPlugins = PluginDependencyUtils.sortCandidates(found); + + // Now load the plugins + pluginLoad: + for (PluginDescription plugin : sortedPlugins) { + // Verify dependencies + for (PluginDependency dependency : plugin.getDependencies()) { + if (!dependency.isOptional() && !isLoaded(dependency.getId())) { + logger.error("Can't load plugin {} due to missing dependency {}", plugin.getId(), dependency.getId()); + continue pluginLoad; + } + } + + // Actually create the plugin + PluginContainer pluginObject; + + try { + pluginObject = loader.createPlugin(plugin); + } catch (Exception e) { + logger.error("Can't create plugin {}", plugin.getId(), e); + continue; + } + + registerPlugin(pluginObject); + } + } + + @Override + public @NonNull Optional fromInstance(@NonNull Object instance) { + checkNotNull(instance, "instance"); + + if (instance instanceof PluginContainer) { + return Optional.of((PluginContainer) instance); + } + + return Optional.ofNullable(pluginInstances.get(instance)); + } + + @Override + public @NonNull Optional getPlugin(@NonNull String id) { + checkNotNull(id, "id"); + return Optional.ofNullable(plugins.get(id)); + } + + @Override + public @NonNull Collection getPlugins() { + return Collections.unmodifiableCollection(plugins.values()); + } + + @Override + public boolean isLoaded(@NonNull String id) { + return plugins.containsKey(id); + } + + @Override + public void addToClasspath(@NonNull Object plugin, @NonNull Path path) { + checkNotNull(plugin, "instance"); + checkNotNull(path, "path"); + checkArgument(pluginInstances.containsKey(plugin), "plugin is not loaded"); + + ClassLoader pluginClassloader = plugin.getClass().getClassLoader(); + if (pluginClassloader instanceof PluginClassLoader) { + ((PluginClassLoader) pluginClassloader).addPath(path); + } else { + throw new UnsupportedOperationException("Operation is not supported on non-Java Velocity plugins."); + } + } +} 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 new file mode 100644 index 000000000..bcbd0d36a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java @@ -0,0 +1,129 @@ +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.meta.PluginDependency; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.plugin.PluginClassLoader; +import com.velocitypowered.proxy.plugin.loader.java.JavaVelocityPluginDescription; +import com.velocitypowered.proxy.plugin.loader.java.SerializedPluginDescription; +import com.velocitypowered.proxy.plugin.loader.java.VelocityPluginModule; + +import javax.annotation.Nonnull; +import java.io.BufferedInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.regex.Pattern; + +public class JavaPluginLoader implements PluginLoader { + private final ProxyServer server; + private final Path baseDirectory; + + public JavaPluginLoader(ProxyServer server, Path baseDirectory) { + this.server = server; + this.baseDirectory = baseDirectory; + } + + @Nonnull + @Override + public PluginDescription loadPlugin(Path source) throws Exception { + Optional serialized = getSerializedPluginInfo(source); + + if (!serialized.isPresent()) { + throw new InvalidPluginException("Did not find a valid velocity-info.json."); + } + + PluginClassLoader loader = new PluginClassLoader( + new URL[] {source.toUri().toURL() } + ); + + Class mainClass = loader.loadClass(serialized.get().getMain()); + VelocityPluginDescription description = createDescription(serialized.get(), source, mainClass); + + String pluginId = description.getId(); + Pattern pattern = PluginDescription.ID_PATTERN; + + if (!pattern.matcher(pluginId).matches()) { + throw new InvalidPluginException("Plugin ID '" + pluginId + "' must match pattern " + pattern.pattern()); + } + + return description; + } + + @Nonnull + @Override + public PluginContainer createPlugin(PluginDescription description) throws Exception { + if (!(description instanceof JavaVelocityPluginDescription)) { + throw new IllegalArgumentException("Description provided isn't of the Java plugin loader"); + } + + JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description; + Optional source = javaDescription.getSource(); + + if (!source.isPresent()) { + throw new IllegalArgumentException("No path in plugin description"); + } + + Injector injector = Guice.createInjector(new VelocityPluginModule(server, javaDescription, baseDirectory)); + Object instance = injector.getInstance(javaDescription.getMainClass()); + + return new VelocityPluginContainer( + description.getId(), + description.getVersion(), + description.getAuthor(), + description.getDependencies(), + source.get(), + instance + ); + } + + private Optional getSerializedPluginInfo(Path source) throws Exception { + try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(source)))) { + JarEntry entry; + while ((entry = in.getNextJarEntry()) != null) { + if (entry.getName().equals("velocity-plugin.json")) { + try (Reader pluginInfoReader = new InputStreamReader(in)) { + return Optional.of(VelocityServer.GSON.fromJson(pluginInfoReader, SerializedPluginDescription.class)); + } + } + } + + return Optional.empty(); + } + } + + private VelocityPluginDescription createDescription(SerializedPluginDescription description, Path source, Class mainClass) { + Set dependencies = new HashSet<>(); + + for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) { + dependencies.add(toDependencyMeta(dependency)); + } + + return new JavaVelocityPluginDescription( + description.getId(), + description.getVersion(), + description.getAuthor(), + dependencies, + source, + mainClass + ); + } + + private static PluginDependency toDependencyMeta(SerializedPluginDescription.Dependency dependency) { + return new PluginDependency( + dependency.getId(), + null, // TODO Implement version matching in dependency annotation + dependency.isOptional() + ); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java new file mode 100644 index 000000000..db2af2d1a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java @@ -0,0 +1,18 @@ +package com.velocitypowered.proxy.plugin.loader; + +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; + +import javax.annotation.Nonnull; +import java.nio.file.Path; + +/** + * This interface is used for loading plugins. + */ +public interface PluginLoader { + @Nonnull + PluginDescription loadPlugin(Path source) throws Exception; + + @Nonnull + PluginContainer createPlugin(PluginDescription plugin) throws Exception; +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java new file mode 100644 index 000000000..b3cb0454a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java @@ -0,0 +1,28 @@ +package com.velocitypowered.proxy.plugin.loader; + +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.meta.PluginDependency; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; + +public class VelocityPluginContainer extends VelocityPluginDescription implements PluginContainer { + private final Object instance; + + public VelocityPluginContainer(String id, String version, String author, Collection dependencies, Path source, Object instance) { + super(id, version, author, dependencies, source); + this.instance = instance; + } + + @Override + public PluginDescription getDescription() { + return this; + } + + @Override + public Optional getInstance() { + return Optional.ofNullable(instance); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java new file mode 100644 index 000000000..4aa490180 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java @@ -0,0 +1,69 @@ +package com.velocitypowered.proxy.plugin.loader; + +import com.google.common.collect.Maps; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.meta.PluginDependency; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class VelocityPluginDescription implements PluginDescription { + private final String id; + private final String version; + private final String author; + private final Map dependencies; + private final Path source; + + public VelocityPluginDescription(String id, String version, String author, Collection dependencies, Path source) { + this.id = checkNotNull(id, "id"); + this.version = checkNotNull(version, "version"); + this.author = checkNotNull(author, "author"); + this.dependencies = Maps.uniqueIndex(dependencies, PluginDependency::getId); + this.source = source; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public String getAuthor() { + return author; + } + + @Override + public Collection getDependencies() { + return dependencies.values(); + } + + @Override + public Optional getDependency(String id) { + return Optional.ofNullable(dependencies.get(id)); + } + + @Override + public Optional getSource() { + return Optional.ofNullable(source); + } + + @Override + public String toString() { + return "VelocityPluginDescription{" + + "id='" + id + '\'' + + ", version='" + version + '\'' + + ", author='" + author + '\'' + + ", dependencies=" + dependencies + + ", source=" + source + + '}'; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java new file mode 100644 index 000000000..1b0ce229b --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java @@ -0,0 +1,22 @@ +package com.velocitypowered.proxy.plugin.loader.java; + +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription; + +import java.nio.file.Path; +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class JavaVelocityPluginDescription extends VelocityPluginDescription { + private final Class mainClass; + + public JavaVelocityPluginDescription(String id, String version, String author, Collection dependencies, Path source, Class mainClass) { + super(id, version, author, dependencies, source); + this.mainClass = checkNotNull(mainClass); + } + + public Class getMainClass() { + return mainClass; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java new file mode 100644 index 000000000..aeaffc867 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java @@ -0,0 +1,125 @@ +package com.velocitypowered.proxy.plugin.loader.java; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class SerializedPluginDescription { + private final String id; + private final String author; + private final String main; + private final String version; + private final List dependencies; + + public SerializedPluginDescription(String id, String author, String main, String version) { + this(id, author, main, version, ImmutableList.of()); + } + + public SerializedPluginDescription(String id, String author, String main, String version, List dependencies) { + this.id = Preconditions.checkNotNull(id, "id"); + this.author = Preconditions.checkNotNull(author, "author"); + this.main = Preconditions.checkNotNull(main, "main"); + this.version = Preconditions.checkNotNull(version, "version"); + this.dependencies = ImmutableList.copyOf(dependencies); + } + + public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { + List dependencies = new ArrayList<>(); + for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { + dependencies.add(new Dependency(dependency.id(), dependency.optional())); + } + return new SerializedPluginDescription(plugin.id(), plugin.author(), qualifiedName, plugin.version(), dependencies); + } + + public String getId() { + return id; + } + + public String getAuthor() { + return author; + } + + public String getMain() { + return main; + } + + public String getVersion() { + return version; + } + + public List getDependencies() { + return dependencies; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SerializedPluginDescription that = (SerializedPluginDescription) o; + return Objects.equals(id, that.id) && + Objects.equals(author, that.author) && + Objects.equals(main, that.main) && + Objects.equals(version, that.version) && + Objects.equals(dependencies, that.dependencies); + } + + @Override + public int hashCode() { + return Objects.hash(id, author, main, version, dependencies); + } + + @Override + public String toString() { + return "SerializedPluginDescription{" + + "id='" + id + '\'' + + ", author='" + author + '\'' + + ", main='" + main + '\'' + + ", version='" + version + '\'' + + ", dependencies=" + dependencies + + '}'; + } + + public static class Dependency { + private final String id; + private final boolean optional; + + public Dependency(String id, boolean optional) { + this.id = id; + this.optional = optional; + } + + public String getId() { + return id; + } + + public boolean isOptional() { + return optional; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Dependency that = (Dependency) o; + return optional == that.optional && + Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id, optional); + } + + @Override + public String toString() { + return "Dependency{" + + "id='" + id + '\'' + + ", optional=" + optional + + '}'; + } + } +} 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 new file mode 100644 index 000000000..cc3ddadc2 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java @@ -0,0 +1,38 @@ +package com.velocitypowered.proxy.plugin.loader.java; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.event.EventManager; +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; + +import java.nio.file.Path; + +public class VelocityPluginModule implements Module { + private final ProxyServer server; + private final JavaVelocityPluginDescription description; + private final Path basePluginPath; + + public VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description, Path basePluginPath) { + this.server = server; + this.description = description; + this.basePluginPath = basePluginPath; + } + + @Override + public void configure(Binder binder) { + binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId())); + binder.bind(ProxyServer.class).toInstance(server); + binder.bind(Path.class).annotatedWith(DataDirectory.class).toInstance(basePluginPath.resolve(description.getId())); + binder.bind(PluginDescription.class).toInstance(description); + binder.bind(PluginManager.class).toInstance(server.getPluginManager()); + binder.bind(EventManager.class).toInstance(server.getEventManager()); + binder.bind(CommandManager.class).toInstance(server.getCommandManager()); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java new file mode 100644 index 000000000..2dcfc79f9 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java @@ -0,0 +1,69 @@ +package com.velocitypowered.proxy.plugin.util; + +import com.google.common.collect.Maps; +import com.google.common.graph.Graph; +import com.google.common.graph.GraphBuilder; +import com.google.common.graph.MutableGraph; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.meta.PluginDependency; + +import java.util.*; + +public class PluginDependencyUtils { + public static List sortCandidates(List candidates) { + // Create our graph, we're going to be using this for Kahn's algorithm. + MutableGraph graph = GraphBuilder.directed().allowsSelfLoops(false).build(); + Map candidateMap = Maps.uniqueIndex(candidates, PluginDescription::getId); + + // Add edges + for (PluginDescription description : candidates) { + graph.addNode(description); + + for (PluginDependency dependency : description.getDependencies()) { + PluginDescription in = candidateMap.get(dependency.getId()); + + if (in != null) { + graph.putEdge(description, in); + } + } + } + + // Find nodes that have no edges + Queue noEdges = getNoDependencyCandidates(graph); + + // Actually run Kahn's algorithm + List sorted = new ArrayList<>(); + while (!noEdges.isEmpty()) { + PluginDescription candidate = noEdges.poll(); + sorted.add(candidate); + + for (PluginDescription node : graph.successors(candidate)) { + graph.removeEdge(node, candidate); + + if (graph.adjacentNodes(node).isEmpty()) { + if (!noEdges.contains(node)) { + noEdges.add(node); + } + } + } + } + + if (!graph.edges().isEmpty()) { + throw new IllegalStateException("Plugin circular dependency found: " + graph.toString()); + } + + return sorted; + } + + public static Queue getNoDependencyCandidates(Graph graph) { + Queue found = new ArrayDeque<>(); + + for (PluginDescription node : graph.nodes()) { + if (graph.outDegree(node) == 0) { + found.add(node); + } + } + + return found; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java index 7e0491b2a..f980ccdb7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java @@ -1,6 +1,6 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.proxy.data.ServerPing; +import com.velocitypowered.api.server.ServerPing; import net.kyori.text.serializer.ComponentSerializers; public class LegacyPingResponse { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/Sleeper.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/Sleeper.java new file mode 100644 index 000000000..b090ad110 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/Sleeper.java @@ -0,0 +1,7 @@ +package com.velocitypowered.proxy.scheduler; + +public interface Sleeper { + void sleep(long ms) throws InterruptedException; + + Sleeper SYSTEM = Thread::sleep; +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java new file mode 100644 index 000000000..5c40a0817 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -0,0 +1,176 @@ +package com.velocitypowered.proxy.scheduler; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.scheduler.ScheduledTask; +import com.velocitypowered.api.scheduler.Scheduler; +import com.velocitypowered.api.scheduler.TaskStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class VelocityScheduler implements Scheduler { + private final PluginManager pluginManager; + private final ExecutorService taskService; + private final Sleeper sleeper; + private final Multimap tasksByPlugin = Multimaps.synchronizedListMultimap( + Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); + + public VelocityScheduler(PluginManager pluginManager, Sleeper sleeper) { + this.pluginManager = pluginManager; + this.sleeper = sleeper; + this.taskService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true) + .setNameFormat("Velocity Task Scheduler - #%d").build()); + } + + @Override + public TaskBuilder buildTask(Object plugin, Runnable runnable) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkNotNull(runnable, "runnable"); + Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "plugin is not registered"); + return new TaskBuilderImpl(plugin, runnable); + } + + public void shutdown() { + for (ScheduledTask task : ImmutableList.copyOf(tasksByPlugin.values())) { + task.cancel(); + } + taskService.shutdown(); + } + + private class TaskBuilderImpl implements TaskBuilder { + private final Object plugin; + private final Runnable runnable; + private long delay; // ms + private long repeat; // ms + + private TaskBuilderImpl(Object plugin, Runnable runnable) { + this.plugin = plugin; + this.runnable = runnable; + } + + @Override + public TaskBuilder delay(int time, TimeUnit unit) { + this.delay = unit.toMillis(time); + return this; + } + + @Override + public TaskBuilder repeat(int time, TimeUnit unit) { + this.repeat = unit.toMillis(time); + return this; + } + + @Override + public TaskBuilder clearDelay() { + this.delay = 0; + return this; + } + + @Override + public TaskBuilder clearRepeat() { + this.repeat = 0; + return this; + } + + @Override + public ScheduledTask schedule() { + VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat); + taskService.execute(task); + tasksByPlugin.put(plugin, task); + return task; + } + } + + private class VelocityTask implements Runnable, ScheduledTask { + private final Object plugin; + private final Runnable runnable; + private final long delay; + private final long repeat; + private volatile TaskStatus status; + private Thread taskThread; + + private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) { + this.plugin = plugin; + this.runnable = runnable; + this.delay = delay; + this.repeat = repeat; + this.status = TaskStatus.SCHEDULED; + } + + @Override + public Object plugin() { + return plugin; + } + + @Override + public TaskStatus status() { + return status; + } + + @Override + public void cancel() { + if (status == TaskStatus.SCHEDULED) { + status = TaskStatus.CANCELLED; + if (taskThread != null) { + taskThread.interrupt(); + } + } + } + + @Override + public void run() { + taskThread = Thread.currentThread(); + if (delay > 0) { + try { + sleeper.sleep(delay); + } catch (InterruptedException e) { + if (status == TaskStatus.CANCELLED) { + onFinish(); + return; + } + } + } + + while (status != TaskStatus.CANCELLED) { + try { + runnable.run(); + } catch (Exception e) { + Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); + } + + if (repeat > 0) { + try { + sleeper.sleep(delay); + } catch (InterruptedException e) { + if (status == TaskStatus.CANCELLED) { + break; + } + } + } else { + status = TaskStatus.FINISHED; + break; + } + } + + onFinish(); + } + + private void onFinish() { + tasksByPlugin.remove(plugin, this); + } + } + + private static class Log { + private static final Logger logger = LogManager.getLogger(VelocityTask.class); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java b/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java new file mode 100644 index 000000000..f8a454e9a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java @@ -0,0 +1,42 @@ +package com.velocitypowered.proxy.util.concurrency; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadFactory; + +/** + * Represents a {@link ThreadFactory} that records the threads it has spawned. + */ +public class ThreadRecorderThreadFactory implements ThreadFactory { + private final ThreadFactory backing; + private final Set threads = ConcurrentHashMap.newKeySet(); + + public ThreadRecorderThreadFactory(ThreadFactory backing) { + this.backing = Preconditions.checkNotNull(backing, "backing"); + } + + @Override + public Thread newThread(Runnable runnable) { + Preconditions.checkNotNull(runnable, "runnable"); + return backing.newThread(() -> { + threads.add(Thread.currentThread()); + try { + runnable.run(); + } finally { + threads.remove(Thread.currentThread()); + } + }); + } + + public boolean currentlyInFactory() { + return threads.contains(Thread.currentThread()); + } + + @VisibleForTesting + int size() { + return threads.size(); + } +} diff --git a/proxy/src/main/resources/log4j2.xml b/proxy/src/main/resources/log4j2.xml index ddaefacb2..0dc941c11 100644 --- a/proxy/src/main/resources/log4j2.xml +++ b/proxy/src/main/resources/log4j2.xml @@ -2,7 +2,13 @@ - + + + + + + diff --git a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java new file mode 100644 index 000000000..d4d7a951a --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java @@ -0,0 +1,51 @@ +package com.velocitypowered.proxy.scheduler; + +import com.velocitypowered.api.scheduler.ScheduledTask; +import com.velocitypowered.api.scheduler.TaskStatus; +import com.velocitypowered.proxy.testutil.FakePluginManager; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +class VelocitySchedulerTest { + // TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep() + + @Test + void buildTask() throws Exception { + VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager(), Sleeper.SYSTEM); + CountDownLatch latch = new CountDownLatch(1); + ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, latch::countDown).schedule(); + latch.await(); + assertEquals(TaskStatus.FINISHED, task.status()); + } + + @Test + void cancelWorks() throws Exception { + VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager(), Sleeper.SYSTEM); + AtomicInteger i = new AtomicInteger(3); + ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, i::decrementAndGet) + .delay(100, TimeUnit.SECONDS) + .schedule(); + task.cancel(); + Thread.sleep(200); + assertEquals(3, i.get()); + assertEquals(TaskStatus.CANCELLED, task.status()); + } + + @Test + void repeatTaskWorks() throws Exception { + VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager(), Sleeper.SYSTEM); + CountDownLatch latch = new CountDownLatch(3); + ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, latch::countDown) + .delay(100, TimeUnit.MILLISECONDS) + .repeat(100, TimeUnit.MILLISECONDS) + .schedule(); + latch.await(); + task.cancel(); + } + +} \ No newline at end of file diff --git a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java new file mode 100644 index 000000000..5e5a30e40 --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java @@ -0,0 +1,92 @@ +package com.velocitypowered.proxy.testutil; + +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.PluginManager; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; + +public class FakePluginManager implements PluginManager { + public static final Object PLUGIN_A = new Object(); + public static final Object PLUGIN_B = new Object(); + + public static final PluginContainer PC_A = new FakePluginContainer("a", PLUGIN_A); + public static final PluginContainer PC_B = new FakePluginContainer("b", PLUGIN_B); + + @Override + public @NonNull Optional fromInstance(@NonNull Object instance) { + if (instance == PLUGIN_A) { + return Optional.of(PC_A); + } else if (instance == PLUGIN_B) { + return Optional.of(PC_B); + } else { + return Optional.empty(); + } + } + + @Override + public @NonNull Optional getPlugin(@NonNull String id) { + switch (id) { + case "a": + return Optional.of(PC_A); + case "b": + return Optional.of(PC_B); + default: + return Optional.empty(); + } + } + + @Override + public @NonNull Collection getPlugins() { + return ImmutableList.of(PC_A, PC_B); + } + + @Override + public boolean isLoaded(@NonNull String id) { + return id.equals("a") || id.equals("b"); + } + + @Override + public void addToClasspath(@NonNull Object plugin, @NonNull Path path) { + throw new UnsupportedOperationException(); + } + + private static class FakePluginContainer implements PluginContainer { + private final String id; + private final Object instance; + + private FakePluginContainer(String id, Object instance) { + this.id = id; + this.instance = instance; + } + + @Override + public @NonNull PluginDescription getDescription() { + return new PluginDescription() { + @Override + public String getId() { + return id; + } + + @Override + public String getVersion() { + return ""; + } + + @Override + public String getAuthor() { + return ""; + } + }; + } + + @Override + public Optional getInstance() { + return Optional.of(instance); + } + } +} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java index 94f0b90cb..36cef2a1e 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java @@ -1,5 +1,6 @@ package com.velocitypowered.proxy.util; +import com.velocitypowered.api.util.UuidUtils; import org.junit.jupiter.api.Test; import java.util.UUID; diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java new file mode 100644 index 000000000..72d026cf4 --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java @@ -0,0 +1,36 @@ +package com.velocitypowered.proxy.util.concurrency; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.*; + +class ThreadRecorderThreadFactoryTest { + + @Test + void newThread() throws Exception { + ThreadRecorderThreadFactory factory = new ThreadRecorderThreadFactory(Executors.defaultThreadFactory()); + CountDownLatch started = new CountDownLatch(1); + CountDownLatch endThread = new CountDownLatch(1); + factory.newThread(() -> { + started.countDown(); + assertTrue(factory.currentlyInFactory()); + assertEquals(1, factory.size()); + try { + endThread.await(); + } catch (InterruptedException e) { + fail(e); + } + }).start(); + started.await(); + assertFalse(factory.currentlyInFactory()); + assertEquals(1, factory.size()); + endThread.countDown(); + + // Wait a little bit to ensure the thread got shut down + Thread.sleep(10); + assertEquals(0, factory.size()); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e3e315775..044dc9fb2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,4 +6,6 @@ include ( ) findProject(':api')?.name = 'velocity-api' findProject(':proxy')?.name = 'velocity-proxy' -findProject(':native')?.name = 'velocity-native' \ No newline at end of file +findProject(':native')?.name = 'velocity-native' + +enableFeaturePreview('STABLE_PUBLISHING') \ No newline at end of file From c4fdac95919441e6b34986e176e867f33b11fb0f Mon Sep 17 00:00:00 2001 From: kashike Date: Tue, 21 Aug 2018 09:09:48 -0700 Subject: [PATCH 53/88] allow more than one author --- .../ap/SerializedPluginDescription.java | 22 +++++++++-------- .../velocitypowered/api/plugin/Plugin.java | 2 +- .../api/plugin/PluginDescription.java | 9 +++---- .../proxy/plugin/loader/JavaPluginLoader.java | 4 ++-- .../loader/VelocityPluginContainer.java | 5 ++-- .../loader/VelocityPluginDescription.java | 13 +++++----- .../java/JavaVelocityPluginDescription.java | 5 ++-- .../java/SerializedPluginDescription.java | 24 ++++++++++--------- .../proxy/testutil/FakePluginManager.java | 6 +++-- 9 files changed, 50 insertions(+), 40 deletions(-) diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java index d22aeeb16..0458aadfe 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java @@ -5,23 +5,25 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.api.plugin.Plugin; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; public class SerializedPluginDescription { private final String id; - private final String author; + private final List authors; private final String main; private final String version; private final List dependencies; - public SerializedPluginDescription(String id, String author, String main, String version) { + public SerializedPluginDescription(String id, List author, String main, String version) { this(id, author, main, version, ImmutableList.of()); } - public SerializedPluginDescription(String id, String author, String main, String version, List dependencies) { + public SerializedPluginDescription(String id, List authors, String main, String version, List dependencies) { this.id = Preconditions.checkNotNull(id, "id"); - this.author = Preconditions.checkNotNull(author, "author"); + this.authors = Preconditions.checkNotNull(authors, "authors"); this.main = Preconditions.checkNotNull(main, "main"); this.version = Preconditions.checkNotNull(version, "version"); this.dependencies = ImmutableList.copyOf(dependencies); @@ -32,15 +34,15 @@ public class SerializedPluginDescription { for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { dependencies.add(new Dependency(dependency.id(), dependency.optional())); } - return new SerializedPluginDescription(plugin.id(), plugin.author(), qualifiedName, plugin.version(), dependencies); + return new SerializedPluginDescription(plugin.id(), Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), qualifiedName, plugin.version(), dependencies); } public String getId() { return id; } - public String getAuthor() { - return author; + public List getAuthors() { + return authors; } public String getMain() { @@ -61,7 +63,7 @@ public class SerializedPluginDescription { if (o == null || getClass() != o.getClass()) return false; SerializedPluginDescription that = (SerializedPluginDescription) o; return Objects.equals(id, that.id) && - Objects.equals(author, that.author) && + Objects.equals(authors, that.authors) && Objects.equals(main, that.main) && Objects.equals(version, that.version) && Objects.equals(dependencies, that.dependencies); @@ -69,14 +71,14 @@ public class SerializedPluginDescription { @Override public int hashCode() { - return Objects.hash(id, author, main, version, dependencies); + return Objects.hash(id, authors, main, version, dependencies); } @Override public String toString() { return "SerializedPluginDescription{" + "id='" + id + '\'' + - ", author='" + author + '\'' + + ", authors='" + authors + '\'' + ", main='" + main + '\'' + ", version='" + version + '\'' + ", dependencies=" + dependencies + diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java index 4f7ab93ed..98cd165f7 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java @@ -33,7 +33,7 @@ public @interface Plugin { * * @return the plugin's author, or empty if unknown */ - String author() default ""; + String[] authors() default ""; /** * The dependencies required to load before this plugin. diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java index a61274e8d..8968ca6b4 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java @@ -5,6 +5,7 @@ import com.velocitypowered.api.plugin.meta.PluginDependency; import java.nio.file.Path; import java.util.Collection; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; @@ -37,12 +38,12 @@ public interface PluginDescription { String getVersion(); /** - * Gets the author of the {@link Plugin} within this container. + * Gets the authors of the {@link Plugin} within this container. * - * @return the plugin author - * @see Plugin#author() + * @return the plugin authors + * @see Plugin#authors() */ - String getAuthor(); + List getAuthors(); /** * Gets a {@link Collection} of all dependencies of the {@link Plugin} within 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 bcbd0d36a..c91fe36ff 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 @@ -80,7 +80,7 @@ public class JavaPluginLoader implements PluginLoader { return new VelocityPluginContainer( description.getId(), description.getVersion(), - description.getAuthor(), + description.getAuthors(), description.getDependencies(), source.get(), instance @@ -112,7 +112,7 @@ public class JavaPluginLoader implements PluginLoader { return new JavaVelocityPluginDescription( description.getId(), description.getVersion(), - description.getAuthor(), + description.getAuthors(), dependencies, source, mainClass diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java index b3cb0454a..f628787c9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java @@ -6,13 +6,14 @@ import com.velocitypowered.api.plugin.meta.PluginDependency; import java.nio.file.Path; import java.util.Collection; +import java.util.List; import java.util.Optional; public class VelocityPluginContainer extends VelocityPluginDescription implements PluginContainer { private final Object instance; - public VelocityPluginContainer(String id, String version, String author, Collection dependencies, Path source, Object instance) { - super(id, version, author, dependencies, source); + public VelocityPluginContainer(String id, String version, List authors, Collection dependencies, Path source, Object instance) { + super(id, version, authors, dependencies, source); this.instance = instance; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java index 4aa490180..79618e56f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java @@ -6,6 +6,7 @@ import com.velocitypowered.api.plugin.meta.PluginDependency; import java.nio.file.Path; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -14,14 +15,14 @@ import static com.google.common.base.Preconditions.checkNotNull; public class VelocityPluginDescription implements PluginDescription { private final String id; private final String version; - private final String author; + private final List authors; private final Map dependencies; private final Path source; - public VelocityPluginDescription(String id, String version, String author, Collection dependencies, Path source) { + public VelocityPluginDescription(String id, String version, List authors, Collection dependencies, Path source) { this.id = checkNotNull(id, "id"); this.version = checkNotNull(version, "version"); - this.author = checkNotNull(author, "author"); + this.authors = checkNotNull(authors, "authors"); this.dependencies = Maps.uniqueIndex(dependencies, PluginDependency::getId); this.source = source; } @@ -37,8 +38,8 @@ public class VelocityPluginDescription implements PluginDescription { } @Override - public String getAuthor() { - return author; + public List getAuthors() { + return authors; } @Override @@ -61,7 +62,7 @@ public class VelocityPluginDescription implements PluginDescription { return "VelocityPluginDescription{" + "id='" + id + '\'' + ", version='" + version + '\'' + - ", author='" + author + '\'' + + ", authors='" + authors + '\'' + ", dependencies=" + dependencies + ", source=" + source + '}'; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java index 1b0ce229b..303afca31 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java @@ -5,14 +5,15 @@ import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription; import java.nio.file.Path; import java.util.Collection; +import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; public class JavaVelocityPluginDescription extends VelocityPluginDescription { private final Class mainClass; - public JavaVelocityPluginDescription(String id, String version, String author, Collection dependencies, Path source, Class mainClass) { - super(id, version, author, dependencies, source); + public JavaVelocityPluginDescription(String id, String version, List authors, Collection dependencies, Path source, Class mainClass) { + super(id, version, authors, dependencies, source); this.mainClass = checkNotNull(mainClass); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java index aeaffc867..f8f2adcb1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java @@ -5,23 +5,25 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.api.plugin.Plugin; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; public class SerializedPluginDescription { private final String id; - private final String author; + private final List authors; private final String main; private final String version; private final List dependencies; - public SerializedPluginDescription(String id, String author, String main, String version) { - this(id, author, main, version, ImmutableList.of()); + public SerializedPluginDescription(String id, List authors, String main, String version) { + this(id, authors, main, version, ImmutableList.of()); } - public SerializedPluginDescription(String id, String author, String main, String version, List dependencies) { + public SerializedPluginDescription(String id, List authors, String main, String version, List dependencies) { this.id = Preconditions.checkNotNull(id, "id"); - this.author = Preconditions.checkNotNull(author, "author"); + this.authors = Preconditions.checkNotNull(authors, "author"); this.main = Preconditions.checkNotNull(main, "main"); this.version = Preconditions.checkNotNull(version, "version"); this.dependencies = ImmutableList.copyOf(dependencies); @@ -32,15 +34,15 @@ public class SerializedPluginDescription { for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { dependencies.add(new Dependency(dependency.id(), dependency.optional())); } - return new SerializedPluginDescription(plugin.id(), plugin.author(), qualifiedName, plugin.version(), dependencies); + return new SerializedPluginDescription(plugin.id(), Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), qualifiedName, plugin.version(), dependencies); } public String getId() { return id; } - public String getAuthor() { - return author; + public List getAuthors() { + return authors; } public String getMain() { @@ -61,7 +63,7 @@ public class SerializedPluginDescription { if (o == null || getClass() != o.getClass()) return false; SerializedPluginDescription that = (SerializedPluginDescription) o; return Objects.equals(id, that.id) && - Objects.equals(author, that.author) && + Objects.equals(authors, that.authors) && Objects.equals(main, that.main) && Objects.equals(version, that.version) && Objects.equals(dependencies, that.dependencies); @@ -69,14 +71,14 @@ public class SerializedPluginDescription { @Override public int hashCode() { - return Objects.hash(id, author, main, version, dependencies); + return Objects.hash(id, authors, main, version, dependencies); } @Override public String toString() { return "SerializedPluginDescription{" + "id='" + id + '\'' + - ", author='" + author + '\'' + + ", authors='" + authors + '\'' + ", main='" + main + '\'' + ", version='" + version + '\'' + ", dependencies=" + dependencies + diff --git a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java index 5e5a30e40..23c83b8b1 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java @@ -8,6 +8,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.nio.file.Path; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Optional; public class FakePluginManager implements PluginManager { @@ -78,8 +80,8 @@ public class FakePluginManager implements PluginManager { } @Override - public String getAuthor() { - return ""; + public List getAuthors() { + return Collections.emptyList(); } }; } From 830b1d4798ce5c5c118d33cad752e639f37e4862 Mon Sep 17 00:00:00 2001 From: Minecrell Date: Tue, 21 Aug 2018 20:37:10 +0200 Subject: [PATCH 54/88] Various improvements to plugin metadata - Add plugin (display) name and plugin URL - Make everything except plugin ID optional (instead of empty string) - Exclude empty properties from generated velocity-plugin.json - Make plugin author list immutable - Other (minor) cleanup and refactoring --- .gitignore | 3 +- .../ap/SerializedPluginDescription.java | 74 +++++++++++-------- .../velocitypowered/api/plugin/Plugin.java | 15 ++++ .../api/plugin/PluginDescription.java | 34 +++++++-- .../api/plugin/meta/PluginDependency.java | 8 +- .../proxy/plugin/loader/JavaPluginLoader.java | 17 ++--- .../loader/VelocityPluginContainer.java | 13 ++-- .../loader/VelocityPluginDescription.java | 30 ++++++-- .../java/JavaVelocityPluginDescription.java | 9 ++- .../java/SerializedPluginDescription.java | 74 +++++++++++-------- .../proxy/testutil/FakePluginManager.java | 19 +---- 11 files changed, 180 insertions(+), 116 deletions(-) diff --git a/.gitignore b/.gitignore index 3fc46f0ef..6b39e43d9 100644 --- a/.gitignore +++ b/.gitignore @@ -130,4 +130,5 @@ gradle-app.setting logs/ /velocity.toml server-icon.png -/bin/ \ No newline at end of file +/bin/ +run/ diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java index 0458aadfe..12109985e 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java @@ -1,8 +1,9 @@ package com.velocitypowered.api.plugin.ap; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Strings; import com.velocitypowered.api.plugin.Plugin; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; import java.util.Arrays; @@ -11,22 +12,23 @@ import java.util.Objects; import java.util.stream.Collectors; public class SerializedPluginDescription { + // @Nullable is used here to make GSON skip these in the serialized file private final String id; - private final List authors; + private final @Nullable String name; + private final @Nullable String version; + private final @Nullable String url; + private final @Nullable List authors; + private final @Nullable List dependencies; private final String main; - private final String version; - private final List dependencies; - public SerializedPluginDescription(String id, List author, String main, String version) { - this(id, author, main, version, ImmutableList.of()); - } - - public SerializedPluginDescription(String id, List authors, String main, String version, List dependencies) { + public SerializedPluginDescription(String id, String name, String version, String url, List authors, List dependencies, String main) { this.id = Preconditions.checkNotNull(id, "id"); - this.authors = Preconditions.checkNotNull(authors, "authors"); + this.name = Strings.emptyToNull(name); + this.version = Strings.emptyToNull(version); + this.url = Strings.emptyToNull(url); + this.authors = authors == null || authors.isEmpty() ? null : authors; + this.dependencies = dependencies == null || dependencies.isEmpty() ? null : dependencies; this.main = Preconditions.checkNotNull(main, "main"); - this.version = Preconditions.checkNotNull(version, "version"); - this.dependencies = ImmutableList.copyOf(dependencies); } public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { @@ -34,54 +36,66 @@ public class SerializedPluginDescription { for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { dependencies.add(new Dependency(dependency.id(), dependency.optional())); } - return new SerializedPluginDescription(plugin.id(), Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), qualifiedName, plugin.version(), dependencies); + return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.url(), + Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), dependencies, qualifiedName); } public String getId() { return id; } - public List getAuthors() { + public @Nullable String getName() { + return name; + } + + public @Nullable String getVersion() { + return version; + } + + public @Nullable String getUrl() { + return url; + } + + public @Nullable List getAuthors() { return authors; } + public @Nullable List getDependencies() { + return dependencies; + } + public String getMain() { return main; } - public String getVersion() { - return version; - } - - public List getDependencies() { - return dependencies; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SerializedPluginDescription that = (SerializedPluginDescription) o; return Objects.equals(id, that.id) && - Objects.equals(authors, that.authors) && - Objects.equals(main, that.main) && + Objects.equals(name, that.name) && Objects.equals(version, that.version) && - Objects.equals(dependencies, that.dependencies); + Objects.equals(url, that.url) && + Objects.equals(authors, that.authors) && + Objects.equals(dependencies, that.dependencies) && + Objects.equals(main, that.main); } @Override public int hashCode() { - return Objects.hash(id, authors, main, version, dependencies); + return Objects.hash(id, name, version, url, authors, dependencies); } - @Override - public String toString() { + @Override public String toString() { return "SerializedPluginDescription{" + "id='" + id + '\'' + - ", authors='" + authors + '\'' + - ", main='" + main + '\'' + + ", name='" + name + '\'' + ", version='" + version + '\'' + + ", url='" + url + '\'' + + ", authors=" + authors + ", dependencies=" + dependencies + + ", main='" + main + '\'' + '}'; } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java index 98cd165f7..3b0ab9fe2 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java @@ -21,6 +21,14 @@ public @interface Plugin { */ String id(); + /** + * The human readable name of the plugin as to be used in descriptions and + * similar things. + * + * @return The plugin name, or an empty string if unknown + */ + String name() default ""; + /** * The version of the plugin. * @@ -28,6 +36,13 @@ public @interface Plugin { */ String version() default ""; + /** + * The URL or website of the plugin. + * + * @return The plugin url, or an empty string if unknown + */ + String url() default ""; + /** * The author of the plugin. * diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java index 8968ca6b4..78a1b5399 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java @@ -1,5 +1,6 @@ package com.velocitypowered.api.plugin; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.velocitypowered.api.plugin.meta.PluginDependency; @@ -7,7 +8,6 @@ import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.regex.Pattern; /** @@ -29,21 +29,45 @@ public interface PluginDescription { */ String getId(); + /** + * Gets the name of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin name, may be empty + * @see Plugin#name() + */ + default Optional getName() { + return Optional.empty(); + } + /** * Gets the version of the {@link Plugin} within this container. * - * @return the plugin version + * @return an {@link Optional} with the plugin version, may be empty * @see Plugin#version() */ - String getVersion(); + default Optional getVersion() { + return Optional.empty(); + } + + /** + * Gets the url or website of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin url, may be empty + * @see Plugin#url() + */ + default Optional getUrl() { + return Optional.empty(); + } /** * Gets the authors of the {@link Plugin} within this container. * - * @return the plugin authors + * @return the plugin authors, may be empty * @see Plugin#authors() */ - List getAuthors(); + default List getAuthors() { + return ImmutableList.of(); + } /** * Gets a {@link Collection} of all dependencies of the {@link Plugin} within diff --git a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java index eb792b9a8..f63b87c5c 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java @@ -3,6 +3,7 @@ package com.velocitypowered.api.plugin.meta; import javax.annotation.Nullable; import java.util.Objects; +import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -36,11 +37,10 @@ public final class PluginDependency { /** * Returns the version this {@link PluginDependency} should match. * - * @return the plugin version, or {@code null} if unspecified + * @return an {@link Optional} with the plugin version, may be empty */ - @Nullable - public String getVersion() { - return version; + public Optional getVersion() { + return Optional.ofNullable(version); } /** 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 c91fe36ff..09ba72397 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 @@ -77,14 +77,7 @@ public class JavaPluginLoader implements PluginLoader { Injector injector = Guice.createInjector(new VelocityPluginModule(server, javaDescription, baseDirectory)); Object instance = injector.getInstance(javaDescription.getMainClass()); - return new VelocityPluginContainer( - description.getId(), - description.getVersion(), - description.getAuthors(), - description.getDependencies(), - source.get(), - instance - ); + return new VelocityPluginContainer(description, instance); } private Optional getSerializedPluginInfo(Path source) throws Exception { @@ -105,13 +98,17 @@ public class JavaPluginLoader implements PluginLoader { private VelocityPluginDescription createDescription(SerializedPluginDescription description, Path source, Class mainClass) { Set dependencies = new HashSet<>(); - for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) { - dependencies.add(toDependencyMeta(dependency)); + if (description.getDependencies() != null) { + for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) { + dependencies.add(toDependencyMeta(dependency)); + } } return new JavaVelocityPluginDescription( description.getId(), + description.getName(), description.getVersion(), + description.getUrl(), description.getAuthors(), dependencies, source, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java index f628787c9..d7cea4c4a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java @@ -2,24 +2,21 @@ package com.velocitypowered.proxy.plugin.loader; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; -import com.velocitypowered.api.plugin.meta.PluginDependency; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; import java.util.Optional; -public class VelocityPluginContainer extends VelocityPluginDescription implements PluginContainer { +public class VelocityPluginContainer implements PluginContainer { + private final PluginDescription description; private final Object instance; - public VelocityPluginContainer(String id, String version, List authors, Collection dependencies, Path source, Object instance) { - super(id, version, authors, dependencies, source); + public VelocityPluginContainer(PluginDescription description, Object instance) { + this.description = description; this.instance = instance; } @Override public PluginDescription getDescription() { - return this; + return this.description; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java index 79618e56f..769ed3c41 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java @@ -1,8 +1,11 @@ package com.velocitypowered.proxy.plugin.loader; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.meta.PluginDependency; +import org.checkerframework.checker.nullness.qual.Nullable; import java.nio.file.Path; import java.util.Collection; @@ -14,15 +17,20 @@ import static com.google.common.base.Preconditions.checkNotNull; public class VelocityPluginDescription implements PluginDescription { private final String id; - private final String version; + private final @Nullable String name; + private final @Nullable String version; + private final @Nullable String url; private final List authors; private final Map dependencies; private final Path source; - public VelocityPluginDescription(String id, String version, List authors, Collection dependencies, Path source) { + public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String url, + @Nullable List authors, Collection dependencies, Path source) { this.id = checkNotNull(id, "id"); - this.version = checkNotNull(version, "version"); - this.authors = checkNotNull(authors, "authors"); + this.name = Strings.emptyToNull(name); + this.version = Strings.emptyToNull(version); + this.url = Strings.emptyToNull(url); + this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors); this.dependencies = Maps.uniqueIndex(dependencies, PluginDependency::getId); this.source = source; } @@ -33,8 +41,18 @@ public class VelocityPluginDescription implements PluginDescription { } @Override - public String getVersion() { - return version; + public Optional getName() { + return Optional.ofNullable(name); + } + + @Override + public Optional getVersion() { + return Optional.ofNullable(version); + } + + @Override + public Optional getUrl() { + return Optional.ofNullable(url); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java index 303afca31..a827dc202 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java @@ -10,14 +10,15 @@ import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; public class JavaVelocityPluginDescription extends VelocityPluginDescription { - private final Class mainClass; + private final Class mainClass; - public JavaVelocityPluginDescription(String id, String version, List authors, Collection dependencies, Path source, Class mainClass) { - super(id, version, authors, dependencies, source); + public JavaVelocityPluginDescription(String id, String name, String version, String url, List authors, + Collection dependencies, Path source, Class mainClass) { + super(id, name, version, url, authors, dependencies, source); this.mainClass = checkNotNull(mainClass); } - public Class getMainClass() { + public Class getMainClass() { return mainClass; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java index f8f2adcb1..9e60f40ef 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java @@ -1,8 +1,9 @@ package com.velocitypowered.proxy.plugin.loader.java; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Strings; import com.velocitypowered.api.plugin.Plugin; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; import java.util.Arrays; @@ -11,22 +12,23 @@ import java.util.Objects; import java.util.stream.Collectors; public class SerializedPluginDescription { + // @Nullable is used here to make GSON skip these in the serialized file private final String id; - private final List authors; + private final @Nullable String name; + private final @Nullable String version; + private final @Nullable String url; + private final @Nullable List authors; + private final @Nullable List dependencies; private final String main; - private final String version; - private final List dependencies; - public SerializedPluginDescription(String id, List authors, String main, String version) { - this(id, authors, main, version, ImmutableList.of()); - } - - public SerializedPluginDescription(String id, List authors, String main, String version, List dependencies) { + public SerializedPluginDescription(String id, String name, String version, String url, List authors, List dependencies, String main) { this.id = Preconditions.checkNotNull(id, "id"); - this.authors = Preconditions.checkNotNull(authors, "author"); + this.name = Strings.emptyToNull(name); + this.version = Strings.emptyToNull(version); + this.url = Strings.emptyToNull(url); + this.authors = authors == null || authors.isEmpty() ? null : authors; + this.dependencies = dependencies == null || dependencies.isEmpty() ? null : dependencies; this.main = Preconditions.checkNotNull(main, "main"); - this.version = Preconditions.checkNotNull(version, "version"); - this.dependencies = ImmutableList.copyOf(dependencies); } public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { @@ -34,54 +36,66 @@ public class SerializedPluginDescription { for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { dependencies.add(new Dependency(dependency.id(), dependency.optional())); } - return new SerializedPluginDescription(plugin.id(), Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), qualifiedName, plugin.version(), dependencies); + return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.url(), + Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), dependencies, qualifiedName); } public String getId() { return id; } - public List getAuthors() { + public @Nullable String getName() { + return name; + } + + public @Nullable String getVersion() { + return version; + } + + public @Nullable String getUrl() { + return url; + } + + public @Nullable List getAuthors() { return authors; } + public @Nullable List getDependencies() { + return dependencies; + } + public String getMain() { return main; } - public String getVersion() { - return version; - } - - public List getDependencies() { - return dependencies; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SerializedPluginDescription that = (SerializedPluginDescription) o; return Objects.equals(id, that.id) && - Objects.equals(authors, that.authors) && - Objects.equals(main, that.main) && + Objects.equals(name, that.name) && Objects.equals(version, that.version) && - Objects.equals(dependencies, that.dependencies); + Objects.equals(url, that.url) && + Objects.equals(authors, that.authors) && + Objects.equals(dependencies, that.dependencies) && + Objects.equals(main, that.main); } @Override public int hashCode() { - return Objects.hash(id, authors, main, version, dependencies); + return Objects.hash(id, name, version, url, authors, dependencies); } - @Override - public String toString() { + @Override public String toString() { return "SerializedPluginDescription{" + "id='" + id + '\'' + - ", authors='" + authors + '\'' + - ", main='" + main + '\'' + + ", name='" + name + '\'' + ", version='" + version + '\'' + + ", url='" + url + '\'' + + ", authors=" + authors + ", dependencies=" + dependencies + + ", main='" + main + '\'' + '}'; } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java index 23c83b8b1..86fb450ea 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java @@ -8,8 +8,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.nio.file.Path; import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.Optional; public class FakePluginManager implements PluginManager { @@ -68,22 +66,7 @@ public class FakePluginManager implements PluginManager { @Override public @NonNull PluginDescription getDescription() { - return new PluginDescription() { - @Override - public String getId() { - return id; - } - - @Override - public String getVersion() { - return ""; - } - - @Override - public List getAuthors() { - return Collections.emptyList(); - } - }; + return () -> id; } @Override From 65de126934b1b4ed408fcdd97ecb7a7252ea0205 Mon Sep 17 00:00:00 2001 From: Jadon Fowler Date: Tue, 21 Aug 2018 11:45:07 -0700 Subject: [PATCH 55/88] Fallback to next server on connection error Closes #11 Co-authored-by: Dylan Keir Signed-off-by: Jadon Fowler --- .../proxy/connection/client/ConnectedPlayer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 ac4220624..ad1d3583a 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 @@ -177,7 +177,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { if (connectedServer == null || connectedServer.getServerInfo().equals(info)) { // The player isn't yet connected to a server or they are already connected to the server // they're disconnected from. - connection.closeWith(Disconnect.create(disconnectReason)); + Optional nextServer = getNextServerToTry(); + if (nextServer.isPresent()) { + createConnectionRequest(nextServer.get()).fireAndForget(); + } else { + connection.closeWith(Disconnect.create(disconnectReason)); + } } else { connection.write(Chat.create(disconnectReason)); } From c43c6cbea200754f0162fe92891000025da73ab0 Mon Sep 17 00:00:00 2001 From: Minecrell Date: Tue, 21 Aug 2018 21:18:10 +0200 Subject: [PATCH 56/88] Add plugin description --- .../plugin/ap/SerializedPluginDescription.java | 18 ++++++++++++++---- .../com/velocitypowered/api/plugin/Plugin.java | 7 +++++++ .../api/plugin/PluginDescription.java | 10 ++++++++++ .../proxy/plugin/loader/JavaPluginLoader.java | 1 + .../loader/VelocityPluginDescription.java | 14 ++++++++++++-- .../java/JavaVelocityPluginDescription.java | 7 ++++--- .../java/SerializedPluginDescription.java | 18 ++++++++++++++---- 7 files changed, 62 insertions(+), 13 deletions(-) diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java index 12109985e..925074cc2 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java @@ -16,15 +16,18 @@ public class SerializedPluginDescription { private final String id; private final @Nullable String name; private final @Nullable String version; + private final @Nullable String description; private final @Nullable String url; private final @Nullable List authors; private final @Nullable List dependencies; private final String main; - public SerializedPluginDescription(String id, String name, String version, String url, List authors, List dependencies, String main) { + public SerializedPluginDescription(String id, String name, String version, String description, String url, + List authors, List dependencies, String main) { this.id = Preconditions.checkNotNull(id, "id"); this.name = Strings.emptyToNull(name); this.version = Strings.emptyToNull(version); + this.description = Strings.emptyToNull(description); this.url = Strings.emptyToNull(url); this.authors = authors == null || authors.isEmpty() ? null : authors; this.dependencies = dependencies == null || dependencies.isEmpty() ? null : dependencies; @@ -36,7 +39,7 @@ public class SerializedPluginDescription { for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { dependencies.add(new Dependency(dependency.id(), dependency.optional())); } - return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.url(), + return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.description(), plugin.url(), Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), dependencies, qualifiedName); } @@ -52,6 +55,10 @@ public class SerializedPluginDescription { return version; } + public @Nullable String getDescription() { + return description; + } + public @Nullable String getUrl() { return url; } @@ -76,6 +83,7 @@ public class SerializedPluginDescription { return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(version, that.version) && + Objects.equals(description, that.description) && Objects.equals(url, that.url) && Objects.equals(authors, that.authors) && Objects.equals(dependencies, that.dependencies) && @@ -84,14 +92,16 @@ public class SerializedPluginDescription { @Override public int hashCode() { - return Objects.hash(id, name, version, url, authors, dependencies); + return Objects.hash(id, name, version, description, url, authors, dependencies); } - @Override public String toString() { + @Override + public String toString() { return "SerializedPluginDescription{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + + ", description='" + description + '\'' + ", url='" + url + '\'' + ", authors=" + authors + ", dependencies=" + dependencies + diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java index 3b0ab9fe2..b3e603f13 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java @@ -36,6 +36,13 @@ public @interface Plugin { */ String version() default ""; + /** + * The description of the plugin, explaining what it can be used for. + * + * @return The plugin description, or an empty string if unknown + */ + String description() default ""; + /** * The URL or website of the plugin. * diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java index 78a1b5399..d789f68c5 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java @@ -49,6 +49,16 @@ public interface PluginDescription { return Optional.empty(); } + /** + * Gets the description of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin description, may be empty + * @see Plugin#description() + */ + default Optional getDescription() { + return Optional.empty(); + } + /** * Gets the url or website of the {@link Plugin} within this container. * 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 09ba72397..e1ed51ff8 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 @@ -108,6 +108,7 @@ public class JavaPluginLoader implements PluginLoader { description.getId(), description.getName(), description.getVersion(), + description.getDescription(), description.getUrl(), description.getAuthors(), dependencies, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java index 769ed3c41..0ee55c112 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java @@ -19,16 +19,18 @@ public class VelocityPluginDescription implements PluginDescription { private final String id; private final @Nullable String name; private final @Nullable String version; + private final @Nullable String description; private final @Nullable String url; private final List authors; private final Map dependencies; private final Path source; - public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String url, + public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String description, @Nullable String url, @Nullable List authors, Collection dependencies, Path source) { this.id = checkNotNull(id, "id"); this.name = Strings.emptyToNull(name); this.version = Strings.emptyToNull(version); + this.description = Strings.emptyToNull(description); this.url = Strings.emptyToNull(url); this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors); this.dependencies = Maps.uniqueIndex(dependencies, PluginDependency::getId); @@ -50,6 +52,11 @@ public class VelocityPluginDescription implements PluginDescription { return Optional.ofNullable(version); } + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } + @Override public Optional getUrl() { return Optional.ofNullable(url); @@ -79,8 +86,11 @@ public class VelocityPluginDescription implements PluginDescription { public String toString() { return "VelocityPluginDescription{" + "id='" + id + '\'' + + ", name='" + name + '\'' + ", version='" + version + '\'' + - ", authors='" + authors + '\'' + + ", description='" + description + '\'' + + ", url='" + url + '\'' + + ", authors=" + authors + ", dependencies=" + dependencies + ", source=" + source + '}'; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java index a827dc202..a73aaec78 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java @@ -2,6 +2,7 @@ package com.velocitypowered.proxy.plugin.loader.java; import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription; +import org.checkerframework.checker.nullness.qual.Nullable; import java.nio.file.Path; import java.util.Collection; @@ -12,9 +13,9 @@ import static com.google.common.base.Preconditions.checkNotNull; public class JavaVelocityPluginDescription extends VelocityPluginDescription { private final Class mainClass; - public JavaVelocityPluginDescription(String id, String name, String version, String url, List authors, - Collection dependencies, Path source, Class mainClass) { - super(id, name, version, url, authors, dependencies, source); + public JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String description, @Nullable String url, + @Nullable List authors, Collection dependencies, Path source, Class mainClass) { + super(id, name, version, description, url, authors, dependencies, source); this.mainClass = checkNotNull(mainClass); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java index 9e60f40ef..b12b1694e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/SerializedPluginDescription.java @@ -16,15 +16,18 @@ public class SerializedPluginDescription { private final String id; private final @Nullable String name; private final @Nullable String version; + private final @Nullable String description; private final @Nullable String url; private final @Nullable List authors; private final @Nullable List dependencies; private final String main; - public SerializedPluginDescription(String id, String name, String version, String url, List authors, List dependencies, String main) { + public SerializedPluginDescription(String id, String name, String version, String description, String url, + List authors, List dependencies, String main) { this.id = Preconditions.checkNotNull(id, "id"); this.name = Strings.emptyToNull(name); this.version = Strings.emptyToNull(version); + this.description = Strings.emptyToNull(description); this.url = Strings.emptyToNull(url); this.authors = authors == null || authors.isEmpty() ? null : authors; this.dependencies = dependencies == null || dependencies.isEmpty() ? null : dependencies; @@ -36,7 +39,7 @@ public class SerializedPluginDescription { for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { dependencies.add(new Dependency(dependency.id(), dependency.optional())); } - return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.url(), + return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.description(), plugin.url(), Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), dependencies, qualifiedName); } @@ -52,6 +55,10 @@ public class SerializedPluginDescription { return version; } + public @Nullable String getDescription() { + return description; + } + public @Nullable String getUrl() { return url; } @@ -76,6 +83,7 @@ public class SerializedPluginDescription { return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(version, that.version) && + Objects.equals(description, that.description) && Objects.equals(url, that.url) && Objects.equals(authors, that.authors) && Objects.equals(dependencies, that.dependencies) && @@ -84,14 +92,16 @@ public class SerializedPluginDescription { @Override public int hashCode() { - return Objects.hash(id, name, version, url, authors, dependencies); + return Objects.hash(id, name, version, description, url, authors, dependencies); } - @Override public String toString() { + @Override + public String toString() { return "SerializedPluginDescription{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + + ", description='" + description + '\'' + ", url='" + url + '\'' + ", authors=" + authors + ", dependencies=" + dependencies + From 2d0c826ec9c8f523757cb48f358ae0e55fe68182 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 21:51:31 -0400 Subject: [PATCH 57/88] Introduce ServerConnection interface This will become very useful for plugin messaging support. --- .../com/velocitypowered/api/proxy/Player.java | 2 +- .../api/proxy/ServerConnection.java | 12 +++++++ .../backend/BackendPlaySessionHandler.java | 32 +++++++++---------- .../backend/LoginSessionHandler.java | 18 +++++------ ...ion.java => VelocityServerConnection.java} | 20 ++++++------ .../connection/client/ConnectedPlayer.java | 17 +++++----- 6 files changed, 58 insertions(+), 43 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java rename proxy/src/main/java/com/velocitypowered/proxy/connection/backend/{ServerConnection.java => VelocityServerConnection.java} (92%) 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 86978ab57..08bf0c440 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -29,7 +29,7 @@ public interface Player extends CommandSource, InboundConnection { * Returns the server that the player is currently connected to. * @return an {@link Optional} the server that the player is connected to, which may be empty */ - Optional getCurrentServer(); + Optional getCurrentServer(); /** * Sends a chat message to the player's client. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java new file mode 100644 index 000000000..8753019d4 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -0,0 +1,12 @@ +package com.velocitypowered.api.proxy; + +import com.velocitypowered.api.server.ServerInfo; + +/** + * Represents a connection to a backend server from the proxy for a client. + */ +public interface ServerConnection { + ServerInfo getServerInfo(); + + Player getPlayer(); +} 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 5d3797fec..3bf35fe17 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 @@ -11,22 +11,22 @@ import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; public class BackendPlaySessionHandler implements MinecraftSessionHandler { - private final ServerConnection connection; + private final VelocityServerConnection connection; - public BackendPlaySessionHandler(ServerConnection connection) { + public BackendPlaySessionHandler(VelocityServerConnection connection) { this.connection = connection; } @Override public void activated() { - VelocityServer.getServer().getEventManager().fireAndForget(new ServerConnectedEvent(connection.getProxyPlayer(), + VelocityServer.getServer().getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo())); } @Override public void handle(MinecraftPacket packet) { //Not handleable packets: Chat, TabCompleteResponse, Respawn, Scoreboard* - if (!connection.getProxyPlayer().isActive()) { + 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(); @@ -34,14 +34,14 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } ClientPlaySessionHandler playerHandler = - (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); + (ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler(); if (packet instanceof KeepAlive) { // Forward onto the player playerHandler.setLastPing(((KeepAlive) packet).getRandomId()); - connection.getProxyPlayer().getConnection().write(packet); + connection.getPlayer().getConnection().write(packet); } else if (packet instanceof Disconnect) { Disconnect original = (Disconnect) packet; - connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), original); + connection.getPlayer().handleConnectionException(connection.getServerInfo(), original); } else if (packet instanceof JoinGame) { playerHandler.handleBackendJoinGame((JoinGame) packet); } else if (packet instanceof BossBar) { @@ -54,7 +54,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { playerHandler.getServerBossBars().remove(bossBar.getUuid()); break; } - connection.getProxyPlayer().getConnection().write(packet); + connection.getPlayer().getConnection().write(packet); } else if (packet instanceof PluginMessage) { PluginMessage pm = (PluginMessage) packet; if (!canForwardPluginMessage(pm)) { @@ -62,20 +62,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } if (PluginMessageUtil.isMCBrand(pm)) { - connection.getProxyPlayer().getConnection().write(PluginMessageUtil.rewriteMCBrand(pm)); + connection.getPlayer().getConnection().write(PluginMessageUtil.rewriteMCBrand(pm)); return; } - connection.getProxyPlayer().getConnection().write(pm); + connection.getPlayer().getConnection().write(pm); } else { // Just forward the packet on. We don't have anything to handle at this time. - connection.getProxyPlayer().getConnection().write(packet); + connection.getPlayer().getConnection().write(packet); } } @Override public void handleUnknown(ByteBuf buf) { - if (!connection.getProxyPlayer().isActive()) { + 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(); @@ -83,19 +83,19 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } ClientPlaySessionHandler playerHandler = - (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); + (ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler(); ByteBuf remapped = playerHandler.getIdRemapper().remap(buf, ProtocolConstants.Direction.CLIENTBOUND); - connection.getProxyPlayer().getConnection().write(remapped); + connection.getPlayer().getConnection().write(remapped); } @Override public void exception(Throwable throwable) { - connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable); + connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable); } private boolean canForwardPluginMessage(PluginMessage message) { ClientPlaySessionHandler playerHandler = - (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); + (ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler(); if (connection.getMinecraftConnection().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) { return message.getChannel().startsWith("MC|") || playerHandler.getClientPluginMsgChannels().contains(message.getChannel()); 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 4cd1cd9c2..cd0513582 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 @@ -25,10 +25,10 @@ import java.security.NoSuchAlgorithmException; import java.util.concurrent.CompletableFuture; public class LoginSessionHandler implements MinecraftSessionHandler { - private final ServerConnection connection; + private final VelocityServerConnection connection; private boolean informationForwarded; - public LoginSessionHandler(ServerConnection connection) { + public LoginSessionHandler(VelocityServerConnection connection) { this.connection = connection; } @@ -45,8 +45,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { response.setSuccess(true); response.setId(message.getId()); response.setData(createForwardingData(configuration.getForwardingSecret(), - connection.getProxyPlayer().getRemoteAddress().getHostString(), - connection.getProxyPlayer().getProfile())); + connection.getPlayer().getRemoteAddress().getHostString(), + connection.getPlayer().getProfile())); connection.getMinecraftConnection().write(response); informationForwarded = true; } else { @@ -76,10 +76,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler { // The player has been logged on to the backend server. connection.getMinecraftConnection().setState(StateRegistry.PLAY); - ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer(); + VelocityServerConnection existingConnection = connection.getPlayer().getConnectedServer(); if (existingConnection == null) { // Strap on the play session handler - connection.getProxyPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(connection.getProxyPlayer())); + connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(connection.getPlayer())); } else { // The previous server connection should become obsolete. existingConnection.disconnect(); @@ -87,14 +87,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { doNotify(ConnectionRequestResults.SUCCESSFUL); connection.getMinecraftConnection().setSessionHandler(new BackendPlaySessionHandler(connection)); - connection.getProxyPlayer().setConnectedServer(connection); + connection.getPlayer().setConnectedServer(connection); } } @Override public void exception(Throwable throwable) { CompletableFuture future = connection.getMinecraftConnection().getChannel() - .attr(ServerConnection.CONNECTION_NOTIFIER).getAndSet(null); + .attr(VelocityServerConnection.CONNECTION_NOTIFIER).getAndSet(null); if (future != null) { future.completeExceptionally(throwable); } @@ -102,7 +102,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { private void doNotify(ConnectionRequestBuilder.Result result) { CompletableFuture future = connection.getMinecraftConnection().getChannel() - .attr(ServerConnection.CONNECTION_NOTIFIER).getAndSet(null); + .attr(VelocityServerConnection.CONNECTION_NOTIFIER).getAndSet(null); if (future != null) { future.complete(result); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java similarity index 92% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 98e4cd72d..e901ead3a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -1,9 +1,10 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; -import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; @@ -31,7 +32,7 @@ import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER; import static com.velocitypowered.network.Connections.READ_TIMEOUT; import static com.velocitypowered.network.Connections.SERVER_READ_TIMEOUT_SECONDS; -public class ServerConnection implements MinecraftConnectionAssociation { +public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection { static final AttributeKey> CONNECTION_NOTIFIER = AttributeKey.newInstance("connection-notification-result"); @@ -40,7 +41,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { private final VelocityServer server; private MinecraftConnection minecraftConnection; - public ServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { + public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { this.serverInfo = target; this.proxyPlayer = proxyPlayer; this.server = server; @@ -62,7 +63,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { ch.attr(CONNECTION_NOTIFIER).set(result); MinecraftConnection connection = new MinecraftConnection(ch); connection.setState(StateRegistry.HANDSHAKE); - connection.setAssociation(ServerConnection.this); + connection.setAssociation(VelocityServerConnection.this); ch.pipeline().addLast(HANDLER, connection); } }) @@ -74,7 +75,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { minecraftConnection = future.channel().pipeline().get(MinecraftConnection.class); // Kick off the connection process - minecraftConnection.setSessionHandler(new LoginSessionHandler(ServerConnection.this)); + minecraftConnection.setSessionHandler(new LoginSessionHandler(VelocityServerConnection.this)); startHandshake(); } else { result.completeExceptionally(future.cause()); @@ -118,10 +119,6 @@ public class ServerConnection implements MinecraftConnectionAssociation { minecraftConnection.write(login); } - public ConnectedPlayer getProxyPlayer() { - return proxyPlayer; - } - public MinecraftConnection getMinecraftConnection() { return minecraftConnection; } @@ -130,6 +127,11 @@ public class ServerConnection implements MinecraftConnectionAssociation { return serverInfo; } + @Override + public ConnectedPlayer getPlayer() { + return proxyPlayer; + } + public void disconnect() { minecraftConnection.close(); minecraftConnection = null; 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 ad1d3583a..4c47e4565 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 @@ -6,6 +6,7 @@ import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionProvider; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; +import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.VelocityServer; @@ -15,7 +16,7 @@ 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.ServerConnection; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.api.server.ServerInfo; @@ -48,9 +49,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private final InetSocketAddress virtualHost; private PermissionFunction permissionFunction = null; private int tryIndex = 0; - private ServerConnection connectedServer; + private VelocityServerConnection connectedServer; private ClientSettings clientSettings; - private ServerConnection connectionInFlight; + private VelocityServerConnection connectionInFlight; public ConnectedPlayer(GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) { this.profile = profile; @@ -69,8 +70,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public Optional getCurrentServer() { - return connectedServer != null ? Optional.of(connectedServer.getServerInfo()) : Optional.empty(); + public Optional getCurrentServer() { + return Optional.ofNullable(connectedServer); } public GameProfile getProfile() { @@ -133,7 +134,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return new ConnectionRequestBuilderImpl(info); } - public ServerConnection getConnectedServer() { + public VelocityServerConnection getConnectedServer() { return connectedServer; } @@ -222,11 +223,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { ); } - return new ServerConnection(newEvent.getResult().getInfo().get(), this, VelocityServer.getServer()).connect(); + return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, VelocityServer.getServer()).connect(); }); } - public void setConnectedServer(ServerConnection serverConnection) { + public void setConnectedServer(VelocityServerConnection serverConnection) { if (this.connectedServer != null && !serverConnection.getServerInfo().equals(connectedServer.getServerInfo())) { this.tryIndex = 0; } From 7b84da2fa709b7e83e170fcf68cacd45236a141a Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 22:29:01 -0400 Subject: [PATCH 58/88] Cleaning up some stuff in the proxy implementation. --- .../velocitypowered/proxy/VelocityServer.java | 20 ++++++++++++------- .../backend/BackendPlaySessionHandler.java | 1 - .../client/ClientPlaySessionHandler.java | 13 ++++++++---- .../client/HandshakeSessionHandler.java | 7 +++++++ .../client/LoginSessionHandler.java | 7 +++++++ .../client/StatusSessionHandler.java | 2 +- .../proxy/plugin/VelocityEventManager.java | 10 +++++----- .../proxy/plugin/VelocityPluginManager.java | 1 - .../protocol/remap/EntityIdRemapper.java | 2 +- .../proxy/scheduler/VelocityScheduler.java | 3 ++- ...ctory.java => RecordingThreadFactory.java} | 11 ++++++---- ...t.java => RecordingThreadFactoryTest.java} | 4 ++-- 12 files changed, 54 insertions(+), 27 deletions(-) rename proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/{ThreadRecorderThreadFactory.java => RecordingThreadFactory.java} (68%) rename proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/{ThreadRecorderThreadFactoryTest.java => RecordingThreadFactoryTest.java} (86%) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 73a15ae9d..c474019e6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -139,14 +139,13 @@ public class VelocityServer implements ProxyServer { scheduler = new VelocityScheduler(pluginManager, Sleeper.SYSTEM); loadPlugins(); - // Post the first event - pluginManager.getPlugins().forEach(container -> { - container.getInstance().ifPresent(plugin -> eventManager.register(plugin, plugin)); - }); try { + // Go ahead and fire the proxy initialization event. We block since plugins should have a chance + // to fully initialize before we accept any connections to the server. eventManager.fire(new ProxyInitializeEvent()).get(); } catch (InterruptedException | ExecutionException e) { - // Ignore, we don't care. + // Ignore, we don't care. InterruptedException is unlikely to happen (and if it does, you've got bigger + // issues) and there is almost no chance ExecutionException will be thrown. } this.cm.bind(configuration.getBind()); @@ -176,6 +175,11 @@ public class VelocityServer implements ProxyServer { logger.error("Couldn't load plugins", e); } + // Register the plugin main classes so that we may proceed with firing the proxy initialize event + pluginManager.getPlugins().forEach(container -> { + container.getInstance().ifPresent(plugin -> eventManager.register(plugin, plugin)); + }); + logger.info("Loaded {} plugins", pluginManager.getPlugins().size()); } @@ -203,9 +207,11 @@ public class VelocityServer implements ProxyServer { eventManager.fire(new ProxyShutdownEvent()); try { - eventManager.shutdown(); + if (!eventManager.shutdown() || scheduler.shutdown()) { + logger.error("Your plugins took over 10 seconds to shut down."); + } } catch (InterruptedException e) { - logger.error("Your plugins took over 10 seconds to shut down."); + // Not much we can do about this... } shutdown = true; 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 3bf35fe17..b48aa2dcd 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 @@ -25,7 +25,6 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { - //Not handleable packets: Chat, TabCompleteResponse, Respawn, Scoreboard* if (!connection.getPlayer().isActive()) { // Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // errors. 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 32344337d..2f185f328 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 @@ -21,6 +21,10 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +/** + * Handles communication with the connected Minecraft client. This is effectively the primary nerve center that + * joins backend servers with players. + */ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class); private static final int MAX_PLUGIN_CHANNELS = 128; @@ -53,6 +57,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } if (packet instanceof Chat) { + // Try to handle any commands on the proxy. If that fails, send it onto the client. Chat chat = (Chat) packet; String msg = ((Chat) packet).getMessage(); if (msg.startsWith("/")) { @@ -137,14 +142,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getConnection().delayedWrite(joinGame); idRemapper = EntityIdRemapper.getMapper(joinGame.getEntityId(), player.getConnection().getProtocolVersion()); } else { - // In order to handle switching to another server we will need send three packets: + // Ah, this is the meat and potatoes of the whole venture! + // + // In order to handle switching to another server, you will need to send three packets: // // - The join game packet from the backend server // - A respawn packet with a different dimension // - Another respawn with the correct dimension // - // We can't simply ignore the packet with the different dimension. If you try to be smart about it it doesn't - // work. + // The two respawns with different dimensions are required, otherwise the client gets confused. // // Most notably, by having the client accept the join game packet, we can work around the need to perform // entity ID rewrites, eliminating potential issues from rewriting packets and improving compatibility with @@ -197,7 +203,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } if (actuallyRegistered.size() > 0) { - logger.info("Rewritten register packet: {}", actuallyRegistered); PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered); player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket); } 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 8b089a6ee..9cfa0227f 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 @@ -14,6 +14,8 @@ 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 io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; @@ -71,6 +73,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { } } + @Override + public void handleUnknown(ByteBuf buf) { + throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf)); + } + private void handleLegacy(MinecraftPacket packet) { if (packet instanceof LegacyPing) { VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); 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 c912ba7f0..8fc74b6e0 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 @@ -16,6 +16,8 @@ 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; import io.netty.buffer.Unpooled; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -204,4 +206,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler { inbound.setSessionHandler(new InitialConnectSessionHandler(player)); player.createConnectionRequest(toTry.get()).fireAndForget(); } + + @Override + public void handleUnknown(ByteBuf buf) { + throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf)); + } } 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 bef061769..97d479dec 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 @@ -28,7 +28,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { - Preconditions.checkArgument(packet instanceof StatusPing|| packet instanceof StatusRequest, + Preconditions.checkArgument(packet instanceof StatusPing || packet instanceof StatusRequest, "Unrecognized packet type " + packet.getClass().getName()); if (packet instanceof StatusPing) { 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 fa828883e..3f84e6279 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -9,7 +9,7 @@ import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.plugin.PluginManager; -import com.velocitypowered.proxy.util.concurrency.ThreadRecorderThreadFactory; +import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory; import net.kyori.event.EventSubscriber; import net.kyori.event.PostResult; import net.kyori.event.SimpleEventBus; @@ -35,12 +35,12 @@ public class VelocityEventManager implements EventManager { new ASMEventExecutorFactory<>(new PluginClassLoader(new URL[0])), new VelocityMethodScanner()); private final ExecutorService service; - private final ThreadRecorderThreadFactory recordingThreadFactory; + private final RecordingThreadFactory recordingThreadFactory; private final PluginManager pluginManager; public VelocityEventManager(PluginManager pluginManager) { this.pluginManager = pluginManager; - this.recordingThreadFactory = new ThreadRecorderThreadFactory(new ThreadFactoryBuilder() + this.recordingThreadFactory = new RecordingThreadFactory(new ThreadFactoryBuilder() .setNameFormat("Velocity Event Executor - #%d").setDaemon(true).build()); this.service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), recordingThreadFactory); } @@ -123,9 +123,9 @@ public class VelocityEventManager implements EventManager { bus.unregister(handler); } - public void shutdown() throws InterruptedException { + public boolean shutdown() throws InterruptedException { service.shutdown(); - service.awaitTermination(10, TimeUnit.SECONDS); + return service.awaitTermination(10, TimeUnit.SECONDS); } private static class VelocityEventBus extends SimpleEventBus { 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 85ae7fbf4..074f8940b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -1,6 +1,5 @@ package com.velocitypowered.proxy.plugin; -import com.google.common.base.Preconditions; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginManager; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/remap/EntityIdRemapper.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/remap/EntityIdRemapper.java index ad277d762..c112b344f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/remap/EntityIdRemapper.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/remap/EntityIdRemapper.java @@ -5,7 +5,7 @@ import io.netty.buffer.ByteBuf; /** * Represents a protocol-specific entity ID remapper for certain Minecraft packets. This is mostly required to support - * old versions of Minecraft. For Minecraft 1.9 clients and above, Velocity can use a more efficient method based on + * old versions of Minecraft. For Minecraft 1.8 clients and above, Velocity can use a more efficient method based on * sending JoinGame packets multiple times. */ public interface EntityIdRemapper { 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 5c40a0817..5584e36c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -40,11 +40,12 @@ public class VelocityScheduler implements Scheduler { return new TaskBuilderImpl(plugin, runnable); } - public void shutdown() { + public boolean shutdown() throws InterruptedException { for (ScheduledTask task : ImmutableList.copyOf(tasksByPlugin.values())) { task.cancel(); } taskService.shutdown(); + return taskService.awaitTermination(10, TimeUnit.SECONDS); } private class TaskBuilderImpl implements TaskBuilder { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java b/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java similarity index 68% rename from proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java rename to proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java index f8a454e9a..ae289bb1c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactory.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactory.java @@ -2,19 +2,22 @@ package com.velocitypowered.proxy.util.concurrency; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.MapMaker; +import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; /** - * Represents a {@link ThreadFactory} that records the threads it has spawned. + * A {@link ThreadFactory} that records the threads it has created. Once a thread terminates, it is automatically removed + * from the recorder. */ -public class ThreadRecorderThreadFactory implements ThreadFactory { +public class RecordingThreadFactory implements ThreadFactory { private final ThreadFactory backing; - private final Set threads = ConcurrentHashMap.newKeySet(); + private final Set threads = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); - public ThreadRecorderThreadFactory(ThreadFactory backing) { + public RecordingThreadFactory(ThreadFactory backing) { this.backing = Preconditions.checkNotNull(backing, "backing"); } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactoryTest.java similarity index 86% rename from proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java rename to proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactoryTest.java index 72d026cf4..cac1ef295 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/ThreadRecorderThreadFactoryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/concurrency/RecordingThreadFactoryTest.java @@ -7,11 +7,11 @@ import java.util.concurrent.Executors; import static org.junit.jupiter.api.Assertions.*; -class ThreadRecorderThreadFactoryTest { +class RecordingThreadFactoryTest { @Test void newThread() throws Exception { - ThreadRecorderThreadFactory factory = new ThreadRecorderThreadFactory(Executors.defaultThreadFactory()); + RecordingThreadFactory factory = new RecordingThreadFactory(Executors.defaultThreadFactory()); CountDownLatch started = new CountDownLatch(1); CountDownLatch endThread = new CountDownLatch(1); factory.newThread(() -> { From 2b6786e1faa77812fc11981f9f9ba8b764ef6929 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 22:41:30 -0400 Subject: [PATCH 59/88] Started work on plugin messaging API. --- .../api/proxy/messages/ChannelIdentifier.java | 7 ++ .../api/proxy/messages/ChannelRegistrar.java | 4 ++ .../messages/LegacyChannelIdentifier.java | 43 +++++++++++++ .../messages/MinecraftChannelIdentifier.java | 64 +++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java new file mode 100644 index 000000000..66d8b65c9 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java @@ -0,0 +1,7 @@ +package com.velocitypowered.api.proxy.messages; + +/** + * Represents a kind of channel identifier. + */ +public interface ChannelIdentifier { +} 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 new file mode 100644 index 000000000..71fa1bb1d --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java @@ -0,0 +1,4 @@ +package com.velocitypowered.api.proxy.messages; + +public interface ChannelRegistrar { +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java new file mode 100644 index 000000000..4d534912b --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java @@ -0,0 +1,43 @@ +package com.velocitypowered.api.proxy.messages; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.util.Objects; + +/** + * Reperesents a legacy channel identifier (for Minecraft 1.12 and below). For modern 1.13 plugin messages, please see + * {@link MinecraftChannelIdentifier}. + */ +public class LegacyChannelIdentifier implements ChannelIdentifier { + private final String name; + + public LegacyChannelIdentifier(String name) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "provided name is empty"); + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "LegacyChannelIdentifier{" + + "name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LegacyChannelIdentifier that = (LegacyChannelIdentifier) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} 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 new file mode 100644 index 000000000..839aadb75 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java @@ -0,0 +1,64 @@ +package com.velocitypowered.api.proxy.messages; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Represents a Minecraft 1.13+ channel identifier. + */ +public class MinecraftChannelIdentifier implements ChannelIdentifier { + private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+"); + + private final String namespace; + private final String name; + + private MinecraftChannelIdentifier(String namespace, String name) { + this.namespace = namespace; + this.name = name; + } + + public static MinecraftChannelIdentifier forDefaultNamespace(String name) { + return new MinecraftChannelIdentifier("minecraft", name); + } + + public static MinecraftChannelIdentifier create(String namespace, String name) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "namespace is null or empty"); + Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(), "namespace is not valid"); + Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(), "name is not valid"); + return new MinecraftChannelIdentifier(namespace, name); + } + + public String getNamespace() { + return namespace; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "MinecraftChannelIdentifier{" + + "namespace='" + namespace + '\'' + + ", name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MinecraftChannelIdentifier that = (MinecraftChannelIdentifier) o; + return Objects.equals(namespace, that.namespace) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(namespace, name); + } +} From 3ed499c7c04cd72133848d4062997e51dd1fe35f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 22:45:55 -0400 Subject: [PATCH 60/88] Note that these classes are immutable. --- .../api/proxy/messages/LegacyChannelIdentifier.java | 4 ++-- .../api/proxy/messages/MinecraftChannelIdentifier.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java index 4d534912b..cf660ab5e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java @@ -7,9 +7,9 @@ import java.util.Objects; /** * Reperesents a legacy channel identifier (for Minecraft 1.12 and below). For modern 1.13 plugin messages, please see - * {@link MinecraftChannelIdentifier}. + * {@link MinecraftChannelIdentifier}. This class is immutable and safe for multi-threaded use. */ -public class LegacyChannelIdentifier implements ChannelIdentifier { +public final class LegacyChannelIdentifier implements ChannelIdentifier { private final String name; public LegacyChannelIdentifier(String name) { 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 839aadb75..09b5397d0 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 @@ -7,9 +7,9 @@ import java.util.Objects; import java.util.regex.Pattern; /** - * Represents a Minecraft 1.13+ channel identifier. + * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use. */ -public class MinecraftChannelIdentifier implements ChannelIdentifier { +public final class MinecraftChannelIdentifier implements ChannelIdentifier { private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+"); private final String namespace; From 55041aa1b1cb9a978e1b417f871b6a4b0d99300d Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:03:09 -0400 Subject: [PATCH 61/88] Add basic implementation. --- .../com/velocitypowered/api/proxy/Player.java | 3 +- .../api/proxy/ProxyServer.java | 7 +++ .../api/proxy/ServerConnection.java | 3 +- .../api/proxy/messages/ChannelIdentifier.java | 1 + .../proxy/messages/ChannelMessageSource.java | 4 ++ .../api/proxy/messages/ChannelRegistrar.java | 7 +++ .../api/proxy/messages/ChannelSide.java | 6 +++ .../messages/LegacyChannelIdentifier.java | 5 ++ .../api/proxy/messages/MessageHandler.java | 10 ++++ .../messages/MinecraftChannelIdentifier.java | 5 ++ .../velocitypowered/proxy/VelocityServer.java | 8 ++++ .../backend/BackendPlaySessionHandler.java | 8 +++- .../client/ClientPlaySessionHandler.java | 10 +++- .../messages/VelocityChannelRegistrar.java | 48 +++++++++++++++++++ 14 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java 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 08bf0c440..92871b30e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.api.util.MessagePosition; import net.kyori.text.Component; @@ -12,7 +13,7 @@ import java.util.UUID; /** * Represents a player who is connected to the proxy. */ -public interface Player extends CommandSource, InboundConnection { +public interface Player extends CommandSource, InboundConnection, ChannelMessageSource { /** * Returns the player's current username. * @return the username 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 b35fe127c..433c61097 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -4,6 +4,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandManager; 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.server.ServerInfo; @@ -101,4 +102,10 @@ public interface ProxyServer { * @return the scheduler instance */ Scheduler getScheduler(); + + /** + * Gets the {@link ChannelRegistrar} instance. + * @return the channel registrar + */ + ChannelRegistrar getChannelRegistrar(); } 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 8753019d4..4e18fd949 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -1,11 +1,12 @@ package com.velocitypowered.api.proxy; +import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.server.ServerInfo; /** * Represents a connection to a backend server from the proxy for a client. */ -public interface ServerConnection { +public interface ServerConnection extends ChannelMessageSource { ServerInfo getServerInfo(); Player getPlayer(); diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java index 66d8b65c9..0af1eeae2 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java @@ -4,4 +4,5 @@ package com.velocitypowered.api.proxy.messages; * Represents a kind of channel identifier. */ public interface ChannelIdentifier { + String getId(); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java new file mode 100644 index 000000000..1aaeae943 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java @@ -0,0 +1,4 @@ +package com.velocitypowered.api.proxy.messages; + +public interface ChannelMessageSource { +} 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 71fa1bb1d..412e172df 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,4 +1,11 @@ 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. + */ public interface ChannelRegistrar { + void register(MessageHandler handler, ChannelIdentifier... identifiers); + + void unregister(ChannelIdentifier... identifiers); } 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 new file mode 100644 index 000000000..f27faf71b --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java @@ -0,0 +1,6 @@ +package com.velocitypowered.api.proxy.messages; + +public enum ChannelSide { + FROM_SERVER, + FROM_CLIENT +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java index cf660ab5e..a5a345cfd 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java @@ -40,4 +40,9 @@ public final class LegacyChannelIdentifier implements ChannelIdentifier { public int hashCode() { return Objects.hash(name); } + + @Override + public String getId() { + return name; + } } 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 new file mode 100644 index 000000000..74fe81f82 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java @@ -0,0 +1,10 @@ +package com.velocitypowered.api.proxy.messages; + +public interface MessageHandler { + ForwardStatus handle(ChannelMessageSource source, ChannelSide side, byte[] data); + + enum ForwardStatus { + FORWARD, + 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 09b5397d0..c171fee66 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 @@ -61,4 +61,9 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier { public int hashCode() { return Objects.hash(namespace, name); } + + @Override + public String getId() { + return namespace + ":" + name; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index c474019e6..5a9702c5f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -21,6 +21,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.proxy.command.VelocityCommandManager; +import com.velocitypowered.proxy.messages.VelocityChannelRegistrar; import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.protocol.util.FaviconSerializer; import com.velocitypowered.proxy.plugin.VelocityPluginManager; @@ -84,6 +85,7 @@ public class VelocityServer implements ProxyServer { private Ratelimiter ipAttemptLimiter; private VelocityEventManager eventManager; private VelocityScheduler scheduler; + private VelocityChannelRegistrar channelRegistrar; private VelocityServer() { commandManager.register(new VelocityCommand(), "velocity"); @@ -137,6 +139,7 @@ public class VelocityServer implements ProxyServer { httpClient = new NettyHttpClient(this); eventManager = new VelocityEventManager(pluginManager); scheduler = new VelocityScheduler(pluginManager, Sleeper.SYSTEM); + channelRegistrar = new VelocityChannelRegistrar(); loadPlugins(); try { @@ -304,4 +307,9 @@ public class VelocityServer implements ProxyServer { public VelocityScheduler getScheduler() { return scheduler; } + + @Override + public VelocityChannelRegistrar getChannelRegistrar() { + return channelRegistrar; + } } 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 b48aa2dcd..29e24b2f0 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,6 +1,8 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.messages.ChannelSide; +import com.velocitypowered.api.proxy.messages.MessageHandler; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -65,7 +67,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return; } - connection.getPlayer().getConnection().write(pm); + MessageHandler.ForwardStatus status = VelocityServer.getServer().getChannelRegistrar().handlePluginMessage( + connection, ChannelSide.FROM_SERVER, pm); + if (status == MessageHandler.ForwardStatus.FORWARD) { + connection.getPlayer().getConnection().write(pm); + } } else { // Just forward the packet on. We don't have anything to handle at this time. connection.getPlayer().getConnection().write(packet); 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 2f185f328..0cce4bfc4 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,6 +1,8 @@ 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.proxy.VelocityServer; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -220,8 +222,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return; } - // We're going to forward on the original packet. - player.getConnectedServer().getMinecraftConnection().write(packet); + MessageHandler.ForwardStatus status = VelocityServer.getServer().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); + } } public Set getClientPluginMsgChannels() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java b/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java new file mode 100644 index 000000000..8b74c4917 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java @@ -0,0 +1,48 @@ +package com.velocitypowered.proxy.messages; + +import com.google.common.base.Preconditions; +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 java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class VelocityChannelRegistrar implements ChannelRegistrar { + private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class); + private final Map handlers = new ConcurrentHashMap<>(); + + @Override + public void register(MessageHandler handler, 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); + } + } + + public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) { + MessageHandler handler = handlers.get(message.getChannel()); + if (handler == null) { + // Nothing we can do. + return MessageHandler.ForwardStatus.FORWARD; + } + + try { + return handler.handle(source, side, 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) { + + } +} From bc48fcb9a8c40e91b0aa06051be9497647e80b9f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:26:41 -0400 Subject: [PATCH 62/88] Preparing for maven repo deployment. --- Jenkinsfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 638e40dfe..c416b3bd8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent { docker { image 'openjdk:8-jdk-slim' - args '-v gradle-cache:/root/.gradle:rw' + args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw' } } @@ -18,5 +18,10 @@ pipeline { sh './gradlew test' } } + stage('Deploy Artifacts') { + steps { + sh './gradlew publish' + } + } } } \ No newline at end of file From fd65887f1f19d518a29ab1bf2b80e4b3d544368e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:35:09 -0400 Subject: [PATCH 63/88] Attempt 1 at Maven repo! --- Jenkinsfile | 2 +- api/build.gradle | 17 +++++++++++------ build.gradle | 11 +++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c416b3bd8..b1a184a4a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,7 +20,7 @@ pipeline { } stage('Deploy Artifacts') { steps { - sh './gradlew publish' + sh 'export MAVEN_DEPLOYMENT=true; ./gradlew publish' } } } diff --git a/api/build.gradle b/api/build.gradle index 18ec5eb48..4ea731242 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -58,11 +58,16 @@ publishing { } } - // TODO: Set up a Maven repository on Velocity's infrastructure, preferably something lightweight. - /*repositories { - maven { - name = 'myRepo' - url = "file://${buildDir}/repo" + if (System.getenv("MAVEN_DEPLOYMENT") != null && project.ext.getCurrentBranchName() == "master") { + repositories { + maven { + name = 'myRepo' + if (project.version.toString().endsWith("-SNAPSHOT")) { + url = "file:///maven-repo/snapshots" + } else { + url = "file:///maven-repo/releases" + } + } } - }*/ + } } diff --git a/build.gradle b/build.gradle index 0b46cc4c2..ae5c1ff9a 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,17 @@ allprojects { log4jVersion = '2.11.0' nettyVersion = '4.1.28.Final' guavaVersion = '25.1-jre' + + getCurrentBranchName = { + new ByteArrayOutputStream().withStream { os -> + exec { + executable = "git" + args = ["rev-parse", "--abbrev-ref", "HEAD"] + standardOutput = os + } + return os.toString() + } + } } repositories { From 7263c1046f392fc26047d559e40bf29d72d48b50 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:42:31 -0400 Subject: [PATCH 64/88] Attempt 2 at Maven repo, now with modified Docker base image --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b1a184a4a..f8f1e9f51 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { docker { - image 'openjdk:8-jdk-slim' + image 'velocitypowered/openjdk8-plus-git:slim' args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw' } From 13215f132ef065a9221f3d26f67ad0481b5e8d3a Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:47:29 -0400 Subject: [PATCH 65/88] Debugging Jenkins here... --- api/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/build.gradle b/api/build.gradle index 4ea731242..7032fe084 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -58,6 +58,9 @@ publishing { } } + println "maven deployment: ${System.getenv("MAVEN_DEPLOYMENT")}" + println "on branch ${project.ext.getCurrentBranchName()}" + if (System.getenv("MAVEN_DEPLOYMENT") != null && project.ext.getCurrentBranchName() == "master") { repositories { maven { From f6f48e9b2dce75ad066592b72d4c4319b455ba3e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:58:17 -0400 Subject: [PATCH 66/88] This should do it --- api/build.gradle | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/api/build.gradle b/api/build.gradle index 7032fe084..becadce87 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -58,19 +58,13 @@ publishing { } } - println "maven deployment: ${System.getenv("MAVEN_DEPLOYMENT")}" - println "on branch ${project.ext.getCurrentBranchName()}" - - if (System.getenv("MAVEN_DEPLOYMENT") != null && project.ext.getCurrentBranchName() == "master") { - repositories { - maven { - name = 'myRepo' - if (project.version.toString().endsWith("-SNAPSHOT")) { - url = "file:///maven-repo/snapshots" - } else { - url = "file:///maven-repo/releases" - } - } + repositories { + maven { + name = 'myRepo' + def base = project.ext.getCurrentBranchName() == "master" ? File.createTempDir().toURI().toURL().toString() : 'file:///maven-repo' + def releasesRepoUrl = "$base/releases" + def snapshotsRepoUrl = "$base/snapshots" + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl } } -} +} \ No newline at end of file From 8ea1aef173d5db552b4a38a0376d0768fda76c68 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Aug 2018 23:58:42 -0400 Subject: [PATCH 67/88] I'm crazy --- api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build.gradle b/api/build.gradle index becadce87..afc2c8a6b 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -61,7 +61,7 @@ publishing { repositories { maven { name = 'myRepo' - def base = project.ext.getCurrentBranchName() == "master" ? File.createTempDir().toURI().toURL().toString() : 'file:///maven-repo' + def base = project.ext.getCurrentBranchName() == "master" ? 'file:///maven-repo' : File.createTempDir().toURI().toURL().toString() def releasesRepoUrl = "$base/releases" def snapshotsRepoUrl = "$base/snapshots" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl From d94b58e45e3dfc8d2eaa410c3f49e6c3f2125793 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 00:03:06 -0400 Subject: [PATCH 68/88] One last try, this ought to work. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ae5c1ff9a..7de9e8fc0 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { args = ["rev-parse", "--abbrev-ref", "HEAD"] standardOutput = os } - return os.toString() + return os.toString().trim() } } } From 8352f7fa70b5eb2beb3a1eaf7fbae66bb4f83fa1 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 00:47:29 -0400 Subject: [PATCH 69/88] Add disconnect API call --- api/src/main/java/com/velocitypowered/api/proxy/Player.java | 2 ++ .../proxy/connection/client/ConnectedPlayer.java | 5 +++++ 2 files changed, 7 insertions(+) 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 92871b30e..d4b56cbf3 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -53,4 +53,6 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage * @return a new connection request */ ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); + + void disconnect(Component reason); } 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 4c47e4565..1de683846 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 @@ -134,6 +134,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return new ConnectionRequestBuilderImpl(info); } + @Override + public void disconnect(Component reason) { + connection.closeWith(Disconnect.create(reason)); + } + public VelocityServerConnection getConnectedServer() { return connectedServer; } From c36f417b1ed28d79e2a1e0ebe7961c00255eb74c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 00:53:02 -0400 Subject: [PATCH 70/88] Add support for sending plugin messages over the wire --- .../com/velocitypowered/api/proxy/Player.java | 3 ++- .../api/proxy/ServerConnection.java | 3 ++- .../api/proxy/messages/ChannelMessageSink.java | 5 +++++ .../api/proxy/messages/MessageHandler.java | 2 +- .../backend/VelocityServerConnection.java | 13 +++++++++++++ .../proxy/connection/client/ConnectedPlayer.java | 12 ++++++++++++ .../proxy/messages/VelocityChannelRegistrar.java | 16 +++++++++++++--- 7 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java 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 d4b56cbf3..1029f92bf 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.api.util.MessagePosition; @@ -13,7 +14,7 @@ import java.util.UUID; /** * Represents a player who is connected to the proxy. */ -public interface Player extends CommandSource, InboundConnection, ChannelMessageSource { +public interface Player extends CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink { /** * Returns the player's current username. * @return the username 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 4e18fd949..080930c89 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -1,12 +1,13 @@ package com.velocitypowered.api.proxy; +import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.server.ServerInfo; /** * Represents a connection to a backend server from the proxy for a client. */ -public interface ServerConnection extends ChannelMessageSource { +public interface ServerConnection extends ChannelMessageSource, ChannelMessageSink { ServerInfo getServerInfo(); Player getPlayer(); 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 new file mode 100644 index 000000000..d813c4edc --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java @@ -0,0 +1,5 @@ +package com.velocitypowered.api.proxy.messages; + +public interface ChannelMessageSink { + void sendPluginMessage(ChannelIdentifier identifier, byte[] data); +} 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 index 74fe81f82..52f4b4e1a 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java @@ -1,7 +1,7 @@ package com.velocitypowered.api.proxy.messages; public interface MessageHandler { - ForwardStatus handle(ChannelMessageSource source, ChannelSide side, byte[] data); + ForwardStatus handle(ChannelMessageSource source, ChannelSide side, ChannelIdentifier identifier, byte[] data); enum ForwardStatus { FORWARD, 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 e901ead3a..46516dd17 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 @@ -1,8 +1,10 @@ package com.velocitypowered.proxy.connection.backend; +import com.google.common.base.Preconditions; 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.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -11,6 +13,7 @@ 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.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; @@ -141,4 +144,14 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, public String toString() { return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); } + + @Override + public void 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); + } } 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 1de683846..66d037078 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,6 +7,7 @@ import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionProvider; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.VelocityServer; @@ -18,6 +19,7 @@ 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.util.ThrowableUtils; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.protocol.packet.Disconnect; @@ -263,6 +265,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return permissionFunction.getPermissionSetting(permission).asBoolean(); } + @Override + public void 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); + } + private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final ServerInfo info; 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 8b74c4917..03f6bbb56 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; 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) { @@ -22,18 +23,19 @@ public class VelocityChannelRegistrar implements ChannelRegistrar { 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()); - if (handler == null) { - // Nothing we can do. + ChannelIdentifier identifier = identifierMap.get(message.getChannel()); + if (handler == null || identifier == null) { return MessageHandler.ForwardStatus.FORWARD; } try { - return handler.handle(source, side, message.getData()); + 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. @@ -43,6 +45,14 @@ public class VelocityChannelRegistrar implements ChannelRegistrar { @Override public void unregister(ChannelIdentifier... identifiers) { + for (ChannelIdentifier identifier : identifiers) { + Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier, + "identifier is unknown"); + } + for (ChannelIdentifier identifier : identifiers) { + handlers.remove(identifier.getId()); + identifierMap.remove(identifier.getId()); + } } } From d9c35a680a73f2f95ce206dc4c3eb1b7b95839e2 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 18:59:10 -0400 Subject: [PATCH 71/88] Plugins can get plugin messages now. --- .../backend/BackendPlaySessionHandler.java | 6 +++-- .../client/ClientPlaySessionHandler.java | 23 ++++++++++++++++--- .../messages/VelocityChannelRegistrar.java | 20 ++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) 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 29e24b2f0..be62948cc 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 @@ -103,10 +103,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { (ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler(); if (connection.getMinecraftConnection().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) { return message.getChannel().startsWith("MC|") || - playerHandler.getClientPluginMsgChannels().contains(message.getChannel()); + playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) || + VelocityServer.getServer().getChannelRegistrar().registered(message.getChannel()); } else { return message.getChannel().startsWith("minecraft:") || - playerHandler.getClientPluginMsgChannels().contains(message.getChannel()); + playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) || + VelocityServer.getServer().getChannelRegistrar().registered(message.getChannel()); } } } 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 0cce4bfc4..d5f040d2c 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 @@ -42,6 +42,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { this.player = player; } + @Override + public void activated() { + PluginMessage message; + if (player.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) { + message = PluginMessageUtil.constructChannelsPacket("minecraft:register", VelocityServer.getServer().getChannelRegistrar().getModernChannelIds()); + } else { + message = PluginMessageUtil.constructChannelsPacket("REGISTER", VelocityServer.getServer().getChannelRegistrar().getLegacyChannelIds()); + } + player.getConnection().write(message); + } + @Override public void handle(MinecraftPacket packet) { if (packet instanceof KeepAlive) { @@ -174,11 +185,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { serverBossBars.clear(); // Tell the server about this client's plugin messages. Velocity will forward them on to the client. - if (!clientPluginMsgChannels.isEmpty()) { + Collection toRegister = new HashSet<>(clientPluginMsgChannels); + if (player.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) { + toRegister.addAll(VelocityServer.getServer().getChannelRegistrar().getModernChannelIds()); + } else { + toRegister.addAll(VelocityServer.getServer().getChannelRegistrar().getLegacyChannelIds()); + } + if (!toRegister.isEmpty()) { String channel = player.getConnection().getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13 ? "minecraft:register" : "REGISTER"; - player.getConnectedServer().getMinecraftConnection().delayedWrite( - PluginMessageUtil.constructChannelsPacket(channel, clientPluginMsgChannels)); + player.getConnectedServer().getMinecraftConnection().delayedWrite(PluginMessageUtil.constructChannelsPacket( + channel, toRegister)); } // Flush everything 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 03f6bbb56..dff2f5d3e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/messages/VelocityChannelRegistrar.java @@ -6,8 +6,10 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; public class VelocityChannelRegistrar implements ChannelRegistrar { private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class); @@ -55,4 +57,22 @@ public class VelocityChannelRegistrar implements ChannelRegistrar { identifierMap.remove(identifier.getId()); } } + + public Collection getLegacyChannelIds() { + return identifierMap.values().stream() + .filter(i -> i instanceof LegacyChannelIdentifier) + .map(ChannelIdentifier::getId) + .collect(Collectors.toList()); + } + + public Collection getModernChannelIds() { + return identifierMap.values().stream() + .filter(i -> i instanceof MinecraftChannelIdentifier) + .map(ChannelIdentifier::getId) + .collect(Collectors.toList()); + } + + public boolean registered(String id) { + return identifierMap.containsKey(id); + } } From ccb904eeb13a3e67c2c93472a1da2537261be060 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 20:53:53 -0400 Subject: [PATCH 72/88] Update for Minecraft 1.13.1 support --- .../velocitypowered/proxy/protocol/ProtocolConstants.java | 8 +++++--- .../com/velocitypowered/proxy/protocol/StateRegistry.java | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index 2944949b3..85a11d557 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -17,11 +17,12 @@ public enum ProtocolConstants { ; public static final int MINECRAFT_1_12_1 = 338; public static final int MINECRAFT_1_12_2 = 340; public static final int MINECRAFT_1_13 = 393; + public static final int MINECRAFT_1_13_1 = 401; public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8; - public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13; + public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13_1; - public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13"; + public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13.1"; public static final ImmutableIntArray SUPPORTED_VERSIONS = ImmutableIntArray.of( MINECRAFT_1_8, @@ -35,7 +36,8 @@ public enum ProtocolConstants { ; MINECRAFT_1_12, MINECRAFT_1_12_1, MINECRAFT_1_12_2, - MINECRAFT_1_13 + MINECRAFT_1_13, + MINECRAFT_1_13_1 ); public static boolean isSupported(int version) { 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 6d1d05a12..5f22eca70 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -166,6 +166,7 @@ public enum StateRegistry { LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, ImmutableIntArray.of(MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1)); LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, ImmutableIntArray.of(MINECRAFT_1_12_1)); LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, ImmutableIntArray.of(MINECRAFT_1_12_2)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_13, ImmutableIntArray.of(MINECRAFT_1_13_1)); } private final ProtocolConstants.Direction direction; From 2d21c01f8ae8578d0b572512c87b04e64b5989f2 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 21:08:47 -0400 Subject: [PATCH 73/88] Fix boss bar handling on Minecraft 1.13+. --- .../java/com/velocitypowered/proxy/protocol/StateRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5f22eca70..5ae96abc5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -68,7 +68,8 @@ public enum StateRegistry { CLIENTBOUND.register(BossBar.class, BossBar::new, map(0x0C, MINECRAFT_1_9, false), - map(0x0C, MINECRAFT_1_12, false)); + map(0x0C, MINECRAFT_1_12, false), + map(0x0C, MINECRAFT_1_13, false)); CLIENTBOUND.register(Chat.class, Chat::new, map(0x02, MINECRAFT_1_8, true), map(0x0F, MINECRAFT_1_9, true), From 27760f5a978c238ea4bbdd467f0fa0be1c4ac09b Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 21:10:14 -0400 Subject: [PATCH 74/88] Limit scope of "attempted server connections" to just initial log ons. --- .../proxy/connection/client/ConnectedPlayer.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 66d037078..0d4beb4d4 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 @@ -182,15 +182,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { public void handleConnectionException(ServerInfo info, Component disconnectReason) { connectionInFlight = null; - if (connectedServer == null || connectedServer.getServerInfo().equals(info)) { - // The player isn't yet connected to a server or they are already connected to the server - // they're disconnected from. + if (connectedServer == null) { + // The player isn't yet connected to a server. Optional nextServer = getNextServerToTry(); if (nextServer.isPresent()) { createConnectionRequest(nextServer.get()).fireAndForget(); } else { connection.closeWith(Disconnect.create(disconnectReason)); } + } else if (connectedServer.getServerInfo().equals(info)) { + // Already connected to the server being disconnected from. + // TODO: ServerKickEvent + connection.closeWith(Disconnect.create(disconnectReason)); } else { connection.write(Chat.create(disconnectReason)); } From 8fd026e025497621dfd20f05b777f6487a633f4c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Aug 2018 21:46:17 -0400 Subject: [PATCH 75/88] Totally removed all uses of jsr305. Fixes #38 --- .../api/event/proxy/ProxyPingEvent.java | 4 +--- .../velocitypowered/api/proxy/ProxyServer.java | 11 +++++------ .../com/velocitypowered/proxy/VelocityServer.java | 15 +++++++-------- .../proxy/command/ServerCommand.java | 5 ++--- .../proxy/command/ShutdownCommand.java | 4 +--- .../proxy/command/VelocityCommand.java | 4 +--- .../proxy/connection/client/ConnectedPlayer.java | 3 +-- .../proxy/plugin/loader/JavaPluginLoader.java | 3 --- .../proxy/plugin/loader/PluginLoader.java | 3 --- 9 files changed, 18 insertions(+), 34 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java index 53a7531cc..a4cdf9ef2 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java @@ -4,8 +4,6 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.server.ServerPing; -import javax.annotation.Nonnull; - public class ProxyPingEvent { private final InboundConnection connection; private ServerPing ping; @@ -23,7 +21,7 @@ public class ProxyPingEvent { return ping; } - public void setPing(@Nonnull ServerPing ping) { + public void setPing(ServerPing ping) { this.ping = Preconditions.checkNotNull(ping, "ping"); } 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 433c61097..6877c550d 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -8,7 +8,6 @@ import com.velocitypowered.api.proxy.messages.ChannelRegistrar; import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.server.ServerInfo; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.Optional; import java.util.UUID; @@ -22,14 +21,14 @@ public interface ProxyServer { * @param username the username * @return an {@link Optional} with the player */ - Optional getPlayer(@Nonnull String username); + Optional getPlayer(String username); /** * Retrieves the player currently connected to this proxy by their Minecraft UUID. * @param uuid the UUID * @return an {@link Optional} with the player */ - Optional getPlayer(@Nonnull UUID uuid); + Optional getPlayer(UUID uuid); /** * Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players @@ -49,7 +48,7 @@ public interface ProxyServer { * @param name the name of the server * @return the server */ - Optional getServerInfo(@Nonnull String name); + Optional getServerInfo(String name); /** * Retrieves all {@link ServerInfo}s registered with this proxy. @@ -61,13 +60,13 @@ public interface ProxyServer { * Registers a server with this proxy. A server with this name should not already exist. * @param server the server to register */ - void registerServer(@Nonnull ServerInfo server); + void registerServer(ServerInfo server); /** * Unregisters this server from the proxy. * @param server the server to unregister */ - void unregisterServer(@Nonnull ServerInfo server); + void unregisterServer(ServerInfo server); /** * Returns an instance of {@link CommandSource} that can be used to determine if the command is being invoked by diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 5a9702c5f..dff17b1df 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -39,7 +39,6 @@ import net.kyori.text.serializer.GsonComponentSerializer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import javax.annotation.Nonnull; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -73,12 +72,12 @@ public class VelocityServer implements ProxyServer { private final Map connectionsByName = new ConcurrentHashMap<>(); private final CommandSource consoleCommandSource = new CommandSource() { @Override - public void sendMessage(@Nonnull Component component) { + public void sendMessage(Component component) { logger.info(ComponentSerializers.LEGACY.serialize(component)); } @Override - public boolean hasPermission(@Nonnull String permission) { + public boolean hasPermission(String permission) { return true; } }; @@ -246,13 +245,13 @@ public class VelocityServer implements ProxyServer { } @Override - public Optional getPlayer(@Nonnull String username) { + public Optional getPlayer(String username) { Preconditions.checkNotNull(username, "username"); return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US))); } @Override - public Optional getPlayer(@Nonnull UUID uuid) { + public Optional getPlayer(UUID uuid) { Preconditions.checkNotNull(uuid, "uuid"); return Optional.ofNullable(connectionsByUuid.get(uuid)); } @@ -268,7 +267,7 @@ public class VelocityServer implements ProxyServer { } @Override - public Optional getServerInfo(@Nonnull String name) { + public Optional getServerInfo(String name) { Preconditions.checkNotNull(name, "name"); return servers.getServer(name); } @@ -279,12 +278,12 @@ public class VelocityServer implements ProxyServer { } @Override - public void registerServer(@Nonnull ServerInfo server) { + public void registerServer(ServerInfo server) { servers.register(server); } @Override - public void unregisterServer(@Nonnull ServerInfo server) { + public void unregisterServer(ServerInfo server) { servers.unregister(server); } 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 ee68dbb1d..1a16f34c0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -9,14 +9,13 @@ import com.velocitypowered.proxy.VelocityServer; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; public class ServerCommand implements Command { @Override - public void execute(@Nonnull CommandSource source, @Nonnull String[] args) { + public void execute(CommandSource source, String[] args) { if (!(source instanceof Player)) { source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED)); return; @@ -42,7 +41,7 @@ public class ServerCommand implements Command { } @Override - public List suggest(@Nonnull CommandSource source, @Nonnull String[] currentArgs) { + public List suggest(CommandSource source, String[] currentArgs) { if (currentArgs.length == 0) { return VelocityServer.getServer().getAllServers().stream() .map(ServerInfo::getName) 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 563ecd1fb..41eb3135a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java @@ -6,11 +6,9 @@ import com.velocitypowered.proxy.VelocityServer; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; -import javax.annotation.Nonnull; - public class ShutdownCommand implements Command { @Override - public void execute(@Nonnull CommandSource source, @Nonnull String[] args) { + public void execute(CommandSource source, String[] args) { if (source != VelocityServer.getServer().getConsoleCommandSource()) { source.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED)); return; 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 fd1ae6b13..94825af55 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -7,11 +7,9 @@ import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.format.TextColor; -import javax.annotation.Nonnull; - public class VelocityCommand implements Command { @Override - public void execute(@Nonnull CommandSource source, @Nonnull String[] args) { + public void execute(CommandSource source, String[] args) { String implVersion = VelocityServer.class.getPackage().getImplementationVersion(); TextComponent thisIsVelocity = TextComponent.builder() .content("This is ") 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 0d4beb4d4..e105451fb 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 @@ -33,7 +33,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.NonNull; -import javax.annotation.Nonnull; import java.net.InetSocketAddress; import java.util.List; import java.util.Optional; @@ -264,7 +263,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public boolean hasPermission(@Nonnull String permission) { + public boolean hasPermission(String permission) { return permissionFunction.getPermissionSetting(permission).asBoolean(); } 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 e1ed51ff8..dc26dec3e 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 @@ -11,7 +11,6 @@ import com.velocitypowered.proxy.plugin.loader.java.JavaVelocityPluginDescriptio import com.velocitypowered.proxy.plugin.loader.java.SerializedPluginDescription; import com.velocitypowered.proxy.plugin.loader.java.VelocityPluginModule; -import javax.annotation.Nonnull; import java.io.BufferedInputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -34,7 +33,6 @@ public class JavaPluginLoader implements PluginLoader { this.baseDirectory = baseDirectory; } - @Nonnull @Override public PluginDescription loadPlugin(Path source) throws Exception { Optional serialized = getSerializedPluginInfo(source); @@ -60,7 +58,6 @@ public class JavaPluginLoader implements PluginLoader { return description; } - @Nonnull @Override public PluginContainer createPlugin(PluginDescription description) throws Exception { if (!(description instanceof JavaVelocityPluginDescription)) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java index db2af2d1a..b16e58d0a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java @@ -3,16 +3,13 @@ package com.velocitypowered.proxy.plugin.loader; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; -import javax.annotation.Nonnull; import java.nio.file.Path; /** * This interface is used for loading plugins. */ public interface PluginLoader { - @Nonnull PluginDescription loadPlugin(Path source) throws Exception; - @Nonnull PluginContainer createPlugin(PluginDescription plugin) throws Exception; } From 91265b12a370ba9be1f9832a8586169ab480987d Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 23 Aug 2018 21:51:15 -0400 Subject: [PATCH 76/88] Fix repeat --- .../com/velocitypowered/proxy/scheduler/VelocityScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5584e36c8..fdd577f34 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -151,7 +151,7 @@ public class VelocityScheduler implements Scheduler { if (repeat > 0) { try { - sleeper.sleep(delay); + sleeper.sleep(repeat); } catch (InterruptedException e) { if (status == TaskStatus.CANCELLED) { break; From e6e3ccaa95276e01fc7636cf686c9fd38789ae60 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 23 Aug 2018 21:57:10 -0400 Subject: [PATCH 77/88] Fix minor bug with shutdown messages. --- .../src/main/java/com/velocitypowered/proxy/VelocityServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index dff17b1df..76ef64ab1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -209,7 +209,7 @@ public class VelocityServer implements ProxyServer { eventManager.fire(new ProxyShutdownEvent()); try { - if (!eventManager.shutdown() || scheduler.shutdown()) { + if (!eventManager.shutdown() || !scheduler.shutdown()) { logger.error("Your plugins took over 10 seconds to shut down."); } } catch (InterruptedException e) { From a3c4522ca0409cc1915bcdc3e62a2368cabba491 Mon Sep 17 00:00:00 2001 From: Slava Maspanov Date: Sat, 25 Aug 2018 03:55:15 +0300 Subject: [PATCH 78/88] Allow to enable online mode for player connection (#51) --- .gitignore | 3 + .../api/event/ResultedEvent.java | 3 +- .../api/event/connection/PreLoginEvent.java | 68 +++++++++++++++-- .../gameprofile/GameProfileRequestEvent.java | 53 +++++++++++++ .../com/velocitypowered/api/proxy/Player.java | 3 + .../velocitypowered/api/util/GameProfile.java | 2 +- .../connection/client/ConnectedPlayer.java | 4 +- .../client/LoginSessionHandler.java | 76 +++++++++++-------- 8 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java diff --git a/.gitignore b/.gitignore index 6b39e43d9..fd8f6bff5 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,9 @@ modules.xml **/.settings/ **/bin/ +# NetBeans Gradle# +.nb-gradle/ + # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java index 04f28f390..28bc71c2b 100644 --- a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.event; import com.google.common.base.Preconditions; + import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; import org.checkerframework.checker.nullness.qual.NonNull; @@ -72,7 +73,7 @@ public interface ResultedEvent { private final boolean allowed; private final @Nullable Component reason; - private ComponentResult(boolean allowed, @Nullable Component reason) { + protected ComponentResult(boolean allowed, @Nullable Component reason) { this.allowed = allowed; this.reason = reason; } 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 ad0c9c4a3..9c052df96 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 @@ -2,22 +2,28 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.ResultedEvent.ComponentResult; import com.velocitypowered.api.proxy.InboundConnection; + +import net.kyori.text.Component; + + import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * 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). */ -public class PreLoginEvent implements ResultedEvent { +public class PreLoginEvent implements ResultedEvent { private final InboundConnection connection; private final String username; - private ComponentResult result; + private PreLoginComponentResult result; public PreLoginEvent(InboundConnection connection, String username) { this.connection = Preconditions.checkNotNull(connection, "connection"); this.username = Preconditions.checkNotNull(username, "username"); - this.result = ComponentResult.allowed(); + this.result = PreLoginComponentResult.allowed(); } public InboundConnection getConnection() { @@ -29,12 +35,12 @@ public class PreLoginEvent implements ResultedEvent { logger.error("Unable to enable encryption", exception); @@ -125,20 +127,24 @@ public class LoginSessionHandler implements MinecraftSessionHandler { PreLoginEvent event = new PreLoginEvent(apiInbound, login.getUsername()); VelocityServer.getServer().getEventManager().fire(event) .thenRunAsync(() -> { - if (!event.getResult().isAllowed()) { + if (inbound.isClosed()) { + // The player was disconnected + return; + } + PreLoginComponentResult result = event.getResult(); + if (!result.isAllowed()) { // The component is guaranteed to be provided if the connection was denied. inbound.closeWith(Disconnect.create(event.getResult().getReason().get())); return; } - - if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { + + if (VelocityServer.getServer().getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) { // Request encryption. EncryptionRequest request = generateRequest(); this.verify = Arrays.copyOf(request.getVerifyToken(), 4); inbound.write(request); } else { - // Offline-mode, don't try to request encryption. - initializePlayer(GameProfile.forOfflinePlayer(login.getUsername())); + initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false); } }, inbound.getChannel().eventLoop()); } @@ -153,28 +159,38 @@ public class LoginSessionHandler implements MinecraftSessionHandler { return request; } - private void initializePlayer(GameProfile profile) { - // Initiate a regular connection and move over to it. - ConnectedPlayer player = new ConnectedPlayer(profile, inbound, apiInbound.getVirtualHost().orElse(null)); + private void initializePlayer(GameProfile profile, boolean onlineMode) { - // load permissions first - VelocityServer.getServer().getEventManager().fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) - .thenCompose(event -> { - // wait for permissions to load, then set the players permission function - player.setPermissionFunction(event.createFunction(player)); - // then call & wait for the login event - return VelocityServer.getServer().getEventManager().fire(new LoginEvent(player)); - }) - // then complete the connection - .thenAcceptAsync(event -> { - if (!event.getResult().isAllowed()) { - // The component is guaranteed to be provided if the connection was denied. - inbound.closeWith(Disconnect.create(event.getResult().getReason().get())); - return; - } + GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(profile, onlineMode); + + VelocityServer.getServer().getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> { + // Initiate a regular connection and move over to it. + ConnectedPlayer player = new ConnectedPlayer(profileEvent.getGameProfile() == null ? profile : profileEvent.getGameProfile(), + inbound, apiInbound.getVirtualHost().orElse(null)); + + return VelocityServer.getServer().getEventManager().fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) + .thenCompose(event -> { + // wait for permissions to load, then set the players permission function + player.setPermissionFunction(event.createFunction(player)); + // then call & wait for the login event + return VelocityServer.getServer().getEventManager().fire(new LoginEvent(player)); + }) + // then complete the connection + .thenAcceptAsync(event -> { + if (inbound.isClosed()) { + // The player was disconnected + return; + } + if (!event.getResult().isAllowed()) { + // The component is guaranteed to be provided if the connection was denied. + inbound.closeWith(Disconnect.create(event.getResult().getReason().get())); + return; + } + + handleProxyLogin(player); + }, inbound.getChannel().eventLoop()); + }); - handleProxyLogin(player); - }, inbound.getChannel().eventLoop()); } private void handleProxyLogin(ConnectedPlayer player) { From afb6e69388a3805a4fd8aa1f97ef60758535bcac Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 24 Aug 2018 21:09:56 -0400 Subject: [PATCH 79/88] Improve Javadoc and the GameProfileRequestEvent. --- .../api/command/CommandManager.java | 15 ++++++++++ .../api/event/connection/PreLoginEvent.java | 23 +++++++++++---- .../gameprofile/GameProfileRequestEvent.java | 28 +++++++++++++++---- .../client/LoginSessionHandler.java | 3 +- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index 38f031716..040c3d295 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -6,9 +6,24 @@ import org.checkerframework.checker.nullness.qual.NonNull; * Represents an interface to register a command executor with the proxy. */ public interface CommandManager { + /** + * Registers the specified command with the manager with the specified aliases. + * @param command the command to register + * @param aliases the alias to use + */ void register(@NonNull Command command, String... aliases); + /** + * Unregisters a command. + * @param alias the command alias to unregister + */ void unregister(@NonNull String alias); + /** + * Attempts to execute a command from the specified {@code cmdLine}. + * @param source the command's source + * @param cmdLine the command to run + * @return true if the command was found and executed, false if it was not + */ boolean execute(@NonNull CommandSource source, @NonNull String cmdLine); } 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 9c052df96..169427850 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 @@ -2,7 +2,6 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.event.ResultedEvent.ComponentResult; import com.velocitypowered.api.proxy.InboundConnection; import net.kyori.text.Component; @@ -57,15 +56,14 @@ public class PreLoginEvent implements ResultedEvent { // Initiate a regular connection and move over to it. From 5389ec1bafae85f0837de0b64bf9df9ecf446829 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 24 Aug 2018 21:16:48 -0400 Subject: [PATCH 80/88] Fix compile. --- .../velocitypowered/api/event/connection/PreLoginEvent.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 169427850..ef8edfb9f 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 @@ -6,7 +6,6 @@ import com.velocitypowered.api.proxy.InboundConnection; import net.kyori.text.Component; - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -55,7 +54,7 @@ public class PreLoginEvent implements ResultedEvent Date: Fri, 24 Aug 2018 21:30:58 -0400 Subject: [PATCH 81/88] Clean up ConnectedPlayer initialization. --- .../proxy/connection/client/LoginSessionHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 754864b7b..9ef98b109 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 @@ -164,8 +164,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { VelocityServer.getServer().getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> { // Initiate a regular connection and move over to it. - ConnectedPlayer player = new ConnectedPlayer(profileEvent.getGameProfile() == null ? profile : profileEvent.getGameProfile(), - inbound, apiInbound.getVirtualHost().orElse(null)); + ConnectedPlayer player = new ConnectedPlayer(profileEvent.getGameProfile(), inbound, + apiInbound.getVirtualHost().orElse(null)); return VelocityServer.getServer().getEventManager().fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) .thenCompose(event -> { From 1f8c8dcd948e425cbe1f67678141e2f637c3f8b2 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Sat, 25 Aug 2018 04:33:27 +0300 Subject: [PATCH 82/88] Add Header and Footer. Resolves #50 --- .../api/event/ResultedEvent.java | 1 - .../api/event/connection/PreLoginEvent.java | 1 - .../com/velocitypowered/api/proxy/Player.java | 14 ++++- .../velocitypowered/api/util/GameProfile.java | 2 +- .../connection/client/ConnectedPlayer.java | 11 +++- .../client/LoginSessionHandler.java | 7 +-- .../proxy/protocol/StateRegistry.java | 7 +++ .../protocol/packet/HeaderAndFooter.java | 57 +++++++++++++++++++ 8 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java diff --git a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java index 28bc71c2b..43500eebc 100644 --- a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -1,7 +1,6 @@ package com.velocitypowered.api.event; import com.google.common.base.Preconditions; - import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; import org.checkerframework.checker.nullness.qual.NonNull; 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 9c052df96..4c1cef16f 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 @@ -7,7 +7,6 @@ import com.velocitypowered.api.proxy.InboundConnection; import net.kyori.text.Component; - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; 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 e70c37a6d..e1be76ee6 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,16 +1,13 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.server.ServerInfo; -import com.velocitypowered.api.util.GameProfile.Property; import com.velocitypowered.api.util.MessagePosition; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.List; import java.util.Optional; import java.util.UUID; @@ -58,5 +55,16 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage */ ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); + /** + * Sets a header and footer to the player + * @param header component with header + * @param footer component with footer + */ + void setHeaderAndFooter(@NonNull Component header, @NonNull Component footer); + + /** + * Disconnects the player with the reason + * @param reason component with the reason + */ void disconnect(Component reason); } diff --git a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java index 362cdb6fc..f77b991f3 100644 --- a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -25,7 +25,7 @@ public class GameProfile { public UUID idAsUuid() { return UuidUtils.fromUndashed(id); } - + public String getName() { return name; } 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 4653bd4cc..ac65b4571 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 @@ -9,14 +9,12 @@ import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.util.MessagePosition; -import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; 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.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; @@ -25,6 +23,8 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.api.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; @@ -137,6 +137,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return new ConnectionRequestBuilderImpl(info); } + @Override + public void setHeaderAndFooter(@NonNull Component header, @NonNull Component footer) { + Preconditions.checkNotNull(header, "header"); + Preconditions.checkNotNull(footer, "footer"); + connection.write(HeaderAndFooter.create(header, footer)); + } + @Override public void disconnect(Component reason) { connection.closeWith(Disconnect.create(reason)); 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 0765bcdcd..2757f3be6 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 @@ -10,7 +10,6 @@ import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.server.ServerInfo; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; @@ -100,14 +99,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { // The player disconnected after we authenticated them. return; } + try { inbound.enableEncryption(decryptedSharedSecret); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } - + initializePlayer(VelocityServer.GSON.fromJson(profileResponse, GameProfile.class), true); - }, inbound.getChannel().eventLoop()) .exceptionally(exception -> { logger.error("Unable to enable encryption", exception); @@ -137,7 +136,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { inbound.closeWith(Disconnect.create(event.getResult().getReason().get())); return; } - + if (VelocityServer.getServer().getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) { // Request encryption. EncryptionRequest request = generateRequest(); 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 5ae96abc5..701ec301d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -106,6 +106,13 @@ public enum StateRegistry { map(0x34, MINECRAFT_1_12, true), map(0x35, MINECRAFT_1_12_2, true), map(0x38, MINECRAFT_1_13, true)); + CLIENTBOUND.register(HeaderAndFooter.class, HeaderAndFooter::new, + map(0x47, MINECRAFT_1_8, true), + map(0x48, MINECRAFT_1_9, true), + map(0x47, MINECRAFT_1_9_4, true), + map(0x49, MINECRAFT_1_12, true), + map(0x4A, MINECRAFT_1_12_1, true), + map(0x4E, MINECRAFT_1_13, true)); CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new, map(0x3D, MINECRAFT_1_8, true), map(0x38, MINECRAFT_1_9, true), 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 new file mode 100644 index 000000000..2b3141cb1 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java @@ -0,0 +1,57 @@ +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; + +public class HeaderAndFooter implements MinecraftPacket { + + private String header; + private String footer; + + public HeaderAndFooter() { + } + + public HeaderAndFooter(String header, String footer) { + this.header = header; + this.footer = footer; + } + + public String getHeader() { + return header; + } + + public void setHeader(String header) { + this.header = header; + } + + public String getFooter() { + return footer; + } + + public void setFooter(String footer) { + this.footer = footer; + } + + @Override + public void decode(ByteBuf buf, Direction direction, int protocolVersion) { + // We dont handle this packet from backend + } + + @Override + public void encode(ByteBuf buf, Direction direction, int protocolVersion) { + writeString(buf, header); + writeString(buf, footer); + } + + public static HeaderAndFooter create(Component header, Component footer) { + ComponentSerializer json = ComponentSerializers.JSON; + return new HeaderAndFooter(json.serialize(header), json.serialize(footer)); + } + +} From d65e4ed29682b4330914c3ef4ce13d09edd59bd4 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Sat, 25 Aug 2018 04:45:10 +0300 Subject: [PATCH 83/88] add method to clear header and footer --- .../main/java/com/velocitypowered/api/proxy/Player.java | 9 +++++++-- .../proxy/connection/client/ConnectedPlayer.java | 5 +++++ .../proxy/protocol/packet/HeaderAndFooter.java | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) 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 e1be76ee6..e90a34c13 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -60,8 +60,13 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage * @param header component with header * @param footer component with footer */ - void setHeaderAndFooter(@NonNull Component header, @NonNull Component footer); - + void setHeaderAndFooter(Component header, Component footer); + + /** + * Clears a header and footer for the player + */ + void clearHeaderAndFooter(); + /** * Disconnects the player with the reason * @param reason component with the reason 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 ac65b4571..91c88ce8b 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 @@ -144,6 +144,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { connection.write(HeaderAndFooter.create(header, footer)); } + @Override + public void clearHeaderAndFooter() { + connection.write(HeaderAndFooter.reset()); + } + @Override public void disconnect(Component reason) { connection.closeWith(Disconnect.create(reason)); 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 2b3141cb1..35c75af30 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 @@ -11,6 +11,8 @@ import net.kyori.text.serializer.ComponentSerializers; public class HeaderAndFooter implements MinecraftPacket { + private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}"); + private String header; private String footer; @@ -40,7 +42,7 @@ public class HeaderAndFooter implements MinecraftPacket { @Override public void decode(ByteBuf buf, Direction direction, int protocolVersion) { - // We dont handle this packet from backend + throw new UnsupportedOperationException("Decode is not implemented"); } @Override @@ -54,4 +56,7 @@ public class HeaderAndFooter implements MinecraftPacket { return new HeaderAndFooter(json.serialize(header), json.serialize(footer)); } + public static HeaderAndFooter reset() { + return RESET; + } } From 87ffb1ac2f3fd47708c9aba7e232f699a2891df8 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 24 Aug 2018 21:50:56 -0400 Subject: [PATCH 84/88] Add even more Javadoc. --- .../api/proxy/ServerConnection.java | 8 ++++++++ .../api/proxy/messages/ChannelMessageSink.java | 8 ++++++++ .../proxy/messages/ChannelMessageSource.java | 3 +++ .../api/proxy/messages/ChannelRegistrar.java | 9 +++++++++ .../api/proxy/messages/ChannelSide.java | 9 +++++++++ .../api/proxy/messages/MessageHandler.java | 18 ++++++++++++++++++ .../messages/MinecraftChannelIdentifier.java | 12 ++++++++++++ .../api/scheduler/ScheduledTask.java | 12 ++++++++++++ .../api/scheduler/TaskStatus.java | 9 +++++++++ .../velocitypowered/api/util/GameProfile.java | 15 +++++++++------ 10 files changed, 97 insertions(+), 6 deletions(-) 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 080930c89..5fb44a44a 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -8,7 +8,15 @@ import com.velocitypowered.api.server.ServerInfo; * Represents a connection to a backend server from the proxy for a client. */ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSink { + /** + * Returns the server that this connection is connected to. + * @return the server this connection is connected to + */ ServerInfo getServerInfo(); + /** + * Returns the player that this connection is associated with. + * @return the player for this connection + */ Player getPlayer(); } 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 d813c4edc..57ebe4ea6 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 @@ -1,5 +1,13 @@ package com.velocitypowered.api.proxy.messages; +/** + * Represents something that can send plugin messages. + */ 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 + */ void sendPluginMessage(ChannelIdentifier identifier, byte[] data); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java index 1aaeae943..8cb85ef1c 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java @@ -1,4 +1,7 @@ package com.velocitypowered.api.proxy.messages; +/** + * A marker interface that indicates a source of plugin messages. + */ public interface ChannelMessageSource { } 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 412e172df..2d77988b5 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 @@ -5,7 +5,16 @@ package com.velocitypowered.api.proxy.messages; * the client or the server. */ public interface ChannelRegistrar { + /** + * Registers the specified message handler to listen for plugin messages on the specified channels. + * @param handler the handler to register + * @param identifiers the channel identifiers to register + */ void register(MessageHandler handler, ChannelIdentifier... identifiers); + /** + * Unregisters the handler for the specified channel. + * @param identifiers the identifiers to unregister + */ void unregister(ChannelIdentifier... identifiers); } 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 index f27faf71b..12256f432 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelSide.java @@ -1,6 +1,15 @@ 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 index 52f4b4e1a..70a7e5fa7 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MessageHandler.java @@ -1,10 +1,28 @@ 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 c171fee66..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 @@ -20,10 +20,22 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier { this.name = name; } + /** + * Creates an identifier in the default namespace ({@code minecraft}). Plugins are strongly encouraged to provide + * their own namespace. + * @param name the name in the default namespace to use + * @return a new channel identifier + */ public static MinecraftChannelIdentifier forDefaultNamespace(String name) { return new MinecraftChannelIdentifier("minecraft", name); } + /** + * Creates an identifier in the specified namespace. + * @param namespace the namespace to use + * @param name the channel name inside the specified namespace + * @return a new channel identifier + */ public static MinecraftChannelIdentifier create(String namespace, String name) { Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty"); Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "namespace is null or empty"); diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java b/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java index 58596c684..2ee41380f 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java @@ -4,9 +4,21 @@ package com.velocitypowered.api.scheduler; * Represents a task that is scheduled to run on the proxy. */ public interface ScheduledTask { + /** + * Returns the plugin that scheduled this task. + * @return the plugin that scheduled this task + */ Object plugin(); + /** + * Returns the current status of this task. + * @return the current status of this task + */ TaskStatus status(); + /** + * Cancels this task. If the task is already running, the thread in which it is running will be interrupted. + * If the task is not currently running, Velocity will terminate it safely. + */ void cancel(); } diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java b/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java index b5830fa51..e86f56e12 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java @@ -1,7 +1,16 @@ package com.velocitypowered.api.scheduler; public enum TaskStatus { + /** + * The task is scheduled and is currently running. + */ SCHEDULED, + /** + * The task was cancelled with {@link ScheduledTask#cancel()}. + */ CANCELLED, + /** + * The task has run to completion. This is applicable only for tasks without a repeat. + */ FINISHED } diff --git a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java index f77b991f3..237939ef1 100644 --- a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -7,14 +7,17 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; import java.util.UUID; +/** + * Represents a Mojang game profile. + */ public class GameProfile { private final String id; private final String name; private final List properties; public GameProfile(@NonNull String id, @NonNull String name, @NonNull List properties) { - this.id = id; - this.name = name; + this.id = Preconditions.checkNotNull(id, "id"); + this.name = Preconditions.checkNotNull(name, "name"); this.properties = ImmutableList.copyOf(properties); } @@ -31,7 +34,7 @@ public class GameProfile { } public List getProperties() { - return ImmutableList.copyOf(properties); + return properties; } public static GameProfile forOfflinePlayer(@NonNull String username) { @@ -55,9 +58,9 @@ public class GameProfile { private final String signature; public Property(@NonNull String name, @NonNull String value, @NonNull String signature) { - this.name = name; - this.value = value; - this.signature = signature; + this.name = Preconditions.checkNotNull(name, "name"); + this.value = Preconditions.checkNotNull(value, "value"); + this.signature = Preconditions.checkNotNull(signature, "signature"); } public String getName() { From 4fb415c84586eb50605f523aed7aa6953a19e47f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 24 Aug 2018 21:51:31 -0400 Subject: [PATCH 85/88] A little cleanup on the GameProfileRequestEvent. --- .../event/player/gameprofile/GameProfileRequestEvent.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java index 2630ac5a0..a8df9304d 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java @@ -17,11 +17,11 @@ public class GameProfileRequestEvent { private final boolean onlineMode; private GameProfile gameProfile; - public GameProfileRequestEvent(InboundConnection connection, GameProfile originalProfile, boolean onlinemode) { - this.connection = connection; - this.originalProfile = Preconditions.checkNotNull(originalProfile, "profile"); + public GameProfileRequestEvent(InboundConnection connection, GameProfile originalProfile, boolean onlineMode) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.originalProfile = Preconditions.checkNotNull(originalProfile, "originalProfile"); this.username = originalProfile.getName(); - this.onlineMode = onlinemode; + this.onlineMode = onlineMode; } public InboundConnection getConnection() { From 7ee56d9f5f6c0a7c355b59de095aa559cc7e9286 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 25 Aug 2018 00:29:22 -0400 Subject: [PATCH 86/88] Massively improved Javadoc coverage to prepare for Javadoc site. --- api/build.gradle | 15 +++++++++ .../api/command/package-info.java | 4 +++ .../api/event/ResultedEvent.java | 7 +++- .../api/event/connection/package-info.java | 4 +++ .../api/event/package-info.java | 4 +++ .../api/event/permission/package-info.java | 4 +++ .../GameProfileRequestEvent.java | 6 ++-- .../event/player/ServerConnectedEvent.java | 2 +- .../event/player/ServerPreConnectEvent.java | 2 +- .../api/event/player/package-info.java | 4 +++ .../api/event/proxy/ProxyPingEvent.java | 5 ++- .../api/event/proxy/package-info.java | 4 +++ .../com/velocitypowered/api/package-info.java | 5 --- .../api/permission/package-info.java | 4 +++ .../api/plugin/annotation/package-info.java | 4 +++ .../api/plugin/meta/package-info.java | 4 +++ .../api/plugin/package-info.java | 4 +++ .../api/proxy/ConnectionRequestBuilder.java | 8 ++--- .../api/proxy/InboundConnection.java | 2 +- .../com/velocitypowered/api/proxy/Player.java | 13 ++++---- .../api/proxy/ProxyServer.java | 16 ++++----- .../api/proxy/ServerConnection.java | 2 +- .../api/proxy/messages/ChannelIdentifier.java | 4 +++ .../api/proxy/messages/package-info.java | 4 +++ .../api/proxy/package-info.java | 4 +++ .../api/{ => proxy}/server/ServerInfo.java | 2 +- .../api/{ => proxy}/server/ServerPing.java | 6 +++- .../api/proxy/server/package-info.java | 4 +++ .../api/scheduler/Scheduler.java | 33 +++++++++++++++++++ .../api/scheduler/package-info.java | 4 +++ .../api/{server => util}/Favicon.java | 2 +- .../velocitypowered/api/util/GameProfile.java | 11 +++++-- .../velocitypowered/api/util/UuidUtils.java | 18 ++++++++++ .../api/util/package-info.java | 4 +++ .../velocitypowered/proxy/VelocityServer.java | 6 ++-- .../proxy/command/ServerCommand.java | 2 +- .../proxy/config/VelocityConfiguration.java | 2 +- .../proxy/connection/MinecraftConnection.java | 16 ++++----- .../backend/VelocityServerConnection.java | 17 +++++----- .../connection/client/ConnectedPlayer.java | 2 +- .../client/HandshakeSessionHandler.java | 2 +- .../client/LoginSessionHandler.java | 4 +-- .../client/StatusSessionHandler.java | 2 +- .../network/ConnectionManager.java | 11 ++----- .../{ => proxy}/network/Connections.java | 2 +- .../protocol/packet/LegacyPingResponse.java | 2 +- .../protocol/util/FaviconSerializer.java | 2 +- .../velocitypowered/proxy/util/ServerMap.java | 2 +- .../proxy/util/ServerMapTest.java | 2 +- 49 files changed, 214 insertions(+), 79 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/command/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/connection/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/permission/package-info.java rename api/src/main/java/com/velocitypowered/api/event/player/{gameprofile => }/GameProfileRequestEvent.java (91%) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/proxy/package-info.java delete mode 100644 api/src/main/java/com/velocitypowered/api/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/permission/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/annotation/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/meta/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/plugin/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/package-info.java rename api/src/main/java/com/velocitypowered/api/{ => proxy}/server/ServerInfo.java (97%) rename api/src/main/java/com/velocitypowered/api/{ => proxy}/server/ServerPing.java (97%) create mode 100644 api/src/main/java/com/velocitypowered/api/proxy/server/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/scheduler/package-info.java rename api/src/main/java/com/velocitypowered/api/{server => util}/Favicon.java (98%) create mode 100644 api/src/main/java/com/velocitypowered/api/util/package-info.java rename proxy/src/main/java/com/velocitypowered/{ => proxy}/network/ConnectionManager.java (93%) rename proxy/src/main/java/com/velocitypowered/{ => proxy}/network/Connections.java (94%) diff --git a/api/build.gradle b/api/build.gradle index afc2c8a6b..1c7d350a9 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -48,6 +48,21 @@ artifacts { archives sourcesJar } +javadoc { + options.encoding = 'UTF-8' + options.charSet = 'UTF-8' + options.links( + 'http://www.slf4j.org/apidocs/', + 'https://google.github.io/guava/releases/25.1-jre/api/docs/', + 'https://google.github.io/guice/api-docs/4.2/javadoc/', + 'https://jd.kyori.net/text/1.12-1.6.4/', + 'https://docs.oracle.com/javase/8/docs/api/' + ) + + // Disable the crazy super-strict doclint tool in Java 8 + options.addStringOption('Xdoclint:none', '-quiet') +} + publishing { publications { mavenJava(MavenPublication) { diff --git a/api/src/main/java/com/velocitypowered/api/command/package-info.java b/api/src/main/java/com/velocitypowered/api/command/package-info.java new file mode 100644 index 000000000..1ce9d9c30 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/command/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides a simple command framework. + */ +package com.velocitypowered.api.command; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java index 43500eebc..cf7330180 100644 --- a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -19,7 +19,7 @@ public interface ResultedEvent { R getResult(); /** - * Sets the result of this event. + * Sets the result of this event. The result must be non-null. * @param result the new result */ void setResult(@NonNull R result); @@ -28,6 +28,11 @@ public interface ResultedEvent { * Represents a result for an event. */ interface Result { + /** + * Returns whether or not the event is allowed to proceed. Plugins may choose to skip denied events, and the + * proxy will respect the result of this method. + * @return whether or not the event is allowed to proceed + */ boolean isAllowed(); } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java b/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java new file mode 100644 index 000000000..44e41f8d5 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides events for handling incoming connections to the proxy and loigns. + */ +package com.velocitypowered.api.event.connection; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/event/package-info.java b/api/src/main/java/com/velocitypowered/api/event/package-info.java new file mode 100644 index 000000000..34819f6ba --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides core support for handling events with Velocity. Subpackages include event classes. + */ +package com.velocitypowered.api.event; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/event/permission/package-info.java b/api/src/main/java/com/velocitypowered/api/event/permission/package-info.java new file mode 100644 index 000000000..402d8db84 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/permission/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides events to handle setting up permissions for permission subjects. + */ +package com.velocitypowered.api.event.permission; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java similarity index 91% rename from api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java rename to api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java index a8df9304d..2a02d0a18 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/gameprofile/GameProfileRequestEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java @@ -1,4 +1,4 @@ -package com.velocitypowered.api.event.player.gameprofile; +package com.velocitypowered.api.event.player; import com.velocitypowered.api.proxy.InboundConnection; import org.checkerframework.checker.nullness.qual.Nullable; @@ -43,7 +43,7 @@ public class GameProfileRequestEvent { /** * Returns the game profile that will be used to initialize the connection with. Should no profile be currently * specified, the one generated by the proxy (for offline mode) or retrieved from the Mojang session servers (for - * online mode). + * online mode) will be returned instead. * @return the user's {@link GameProfile} */ public GameProfile getGameProfile() { @@ -51,7 +51,7 @@ public class GameProfileRequestEvent { } /** - * Sets the game profile to use for this connection. Using this method on an online-mode connection is invalid. + * Sets the game profile to use for this connection. It is invalid to use this method on an online-mode connection. * @param gameProfile the profile to use for the connection, {@code null} uses the original profile */ public void setGameProfile(@Nullable GameProfile gameProfile) { diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java index 1c7c3f416..726ceb2e7 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java @@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; /** * This event is fired once the player has successfully connected to the target server and the connection to the previous diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java index a80ce70e3..2d86c06f8 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java @@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/api/src/main/java/com/velocitypowered/api/event/player/package-info.java b/api/src/main/java/com/velocitypowered/api/event/player/package-info.java new file mode 100644 index 000000000..1d0513b60 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides events for handling actions performed by players. + */ +package com.velocitypowered.api.event.player; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java index a4cdf9ef2..e40d810b9 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java @@ -2,8 +2,11 @@ package com.velocitypowered.api.event.proxy; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.InboundConnection; -import com.velocitypowered.api.server.ServerPing; +import com.velocitypowered.api.proxy.server.ServerPing; +/** + * This event is fired when a server list ping request is sent by a remote client. + */ public class ProxyPingEvent { private final InboundConnection connection; private ServerPing ping; diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/package-info.java b/api/src/main/java/com/velocitypowered/api/event/proxy/package-info.java new file mode 100644 index 000000000..3b7aab6b7 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides events for handling the lifecycle of the proxy. + */ +package com.velocitypowered.api.event.proxy; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/package-info.java b/api/src/main/java/com/velocitypowered/api/package-info.java deleted file mode 100644 index 6d8698019..000000000 --- a/api/src/main/java/com/velocitypowered/api/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.velocitypowered.api; - -/** - * Welcome to the Velocity API documentation. - */ \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/permission/package-info.java b/api/src/main/java/com/velocitypowered/api/permission/package-info.java new file mode 100644 index 000000000..a68255248 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/permission/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the basic building blocks for a custom permission system. + */ +package com.velocitypowered.api.permission; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/plugin/annotation/package-info.java b/api/src/main/java/com/velocitypowered/api/plugin/annotation/package-info.java new file mode 100644 index 000000000..81486dfed --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides annotations to handle injecting dependencies for plugins. + */ +package com.velocitypowered.api.plugin.annotation; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/plugin/meta/package-info.java b/api/src/main/java/com/velocitypowered/api/plugin/meta/package-info.java new file mode 100644 index 000000000..cde1887ae --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/meta/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides metadata for plugins. + */ +package com.velocitypowered.api.plugin.meta; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/plugin/package-info.java b/api/src/main/java/com/velocitypowered/api/plugin/package-info.java new file mode 100644 index 000000000..6eea17fd6 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/plugin/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the Velocity plugin API. + */ +package com.velocitypowered.api.plugin; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java b/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java index e4507d752..853b4c3de 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java @@ -1,6 +1,6 @@ package com.velocitypowered.api.proxy; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; @@ -8,8 +8,8 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; /** - * Represents a connection request. A connection request is created using {@link Player#createConnectionRequest(ServerInfo)} - * and is used to allow a plugin to compose and request a connection to another Minecraft server using a fluent API. + * Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection + * request is created using {@link Player#createConnectionRequest(ServerInfo)}. */ public interface ConnectionRequestBuilder { /** @@ -50,7 +50,7 @@ public interface ConnectionRequestBuilder { Status getStatus(); /** - * Returns a reason for the failure to connect to the server. None may be provided. + * Returns an (optional) textual reason for the failure to connect to the server. * @return the reason why the user could not connect to the server */ Optional getReason(); diff --git a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java index 18bd0b421..e3cdcce24 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -4,7 +4,7 @@ import java.net.InetSocketAddress; import java.util.Optional; /** - * Represents a connection to the proxy. There is no guarantee that the connection has been fully initialized. + * Represents an incoming connection to the proxy. */ public interface InboundConnection { /** 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 e90a34c13..dc56e527e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -3,7 +3,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.util.MessagePosition; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; @@ -56,19 +56,20 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); /** - * Sets a header and footer to the player - * @param header component with header - * @param footer component with footer + * Sets the tab list header and footer for the player. + * @param header the header component + * @param footer the footer component */ void setHeaderAndFooter(Component header, Component footer); /** - * Clears a header and footer for the player + * Clears the tab list header and footer for the player. */ void clearHeaderAndFooter(); /** - * Disconnects the player with the reason + * Disconnects the player with the specified reason. Once this method is called, further calls to other {@link Player} + * methods will become undefined. * @param reason component with the reason */ void disconnect(Component reason); 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 6877c550d..a2af42b28 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -6,27 +6,27 @@ 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.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import java.util.Collection; import java.util.Optional; import java.util.UUID; /** - * Represents a Minecraft proxy server that is compatible with the Velocity API. + * Provides an interface to a Minecraft server proxy. */ public interface ProxyServer { /** - * Retrieves the player currently connected to this proxy by their Minecraft username. - * @param username the username - * @return an {@link Optional} with the player + * Retrieves the player currently connected to this proxy by their Minecraft username. The search is case-insensitive. + * @param username the username to search for + * @return an {@link Optional} with the player, which may be empty */ Optional getPlayer(String username); /** * Retrieves the player currently connected to this proxy by their Minecraft UUID. * @param uuid the UUID - * @return an {@link Optional} with the player + * @return an {@link Optional} with the player, which may be empty */ Optional getPlayer(UUID uuid); @@ -44,9 +44,9 @@ public interface ProxyServer { int getPlayerCount(); /** - * Retrieves a registered {@link ServerInfo} instance by its name. + * Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive. * @param name the name of the server - * @return the server + * @return the registered server, which may be empty */ Optional getServerInfo(String name); 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 5fb44a44a..5f12fb326 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -2,7 +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.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; /** * Represents a connection to a backend server from the proxy for a client. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java index 0af1eeae2..4acd5d72a 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java @@ -4,5 +4,9 @@ package com.velocitypowered.api.proxy.messages; * Represents a kind of channel identifier. */ public interface ChannelIdentifier { + /** + * Returns the textual representation of this identifier. + * @return the textual representation of the identifier + */ String getId(); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java new file mode 100644 index 000000000..d55279722 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides an interface to receive, handle, and send plugin messages on the proxy from clients and servers. + */ +package com.velocitypowered.api.proxy.messages; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/proxy/package-info.java b/api/src/main/java/com/velocitypowered/api/proxy/package-info.java new file mode 100644 index 000000000..3a22511ab --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides an interface to interact with the proxy at a low level. + */ +package com.velocitypowered.api.proxy; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/server/ServerInfo.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java similarity index 97% rename from api/src/main/java/com/velocitypowered/api/server/ServerInfo.java rename to api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java index ef83511f1..3dffa7111 100644 --- a/api/src/main/java/com/velocitypowered/api/server/ServerInfo.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java @@ -1,4 +1,4 @@ -package com.velocitypowered.api.server; +package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; import org.checkerframework.checker.nullness.qual.NonNull; diff --git a/api/src/main/java/com/velocitypowered/api/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java similarity index 97% rename from api/src/main/java/com/velocitypowered/api/server/ServerPing.java rename to api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index f7842b412..f48fbc3a8 100644 --- a/api/src/main/java/com/velocitypowered/api/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -1,7 +1,8 @@ -package com.velocitypowered.api.server; +package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.util.Favicon; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -65,6 +66,9 @@ public class ServerPing { return new Builder(); } + /** + * A builder for {@link ServerPing} objects. + */ public static class Builder { private Version version; private int onlinePlayers; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/package-info.java b/api/src/main/java/com/velocitypowered/api/proxy/server/package-info.java new file mode 100644 index 000000000..7f902113a --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides utilities to handle server information. + */ +package com.velocitypowered.api.proxy.server; \ No newline at end of file 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 5565a9e79..fd652d1c6 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java @@ -6,17 +6,50 @@ import java.util.concurrent.TimeUnit; * Represents a scheduler to execute tasks on the proxy. */ public interface Scheduler { + /** + * Initializes a new {@link TaskBuilder} for creating a task on the proxy. + * @param plugin the plugin to request the task for + * @param runnable the task to run when scheduled + * @return the task builder + */ TaskBuilder buildTask(Object plugin, Runnable runnable); + /** + * Represents a fluent interface to schedule tasks on the proxy. + */ interface TaskBuilder { + /** + * Specifies that the task should delay its execution by the specified amount of time. + * @param time the time to delay by + * @param unit the unit of time for {@code time} + * @return this builder, for chaining + */ TaskBuilder delay(int time, TimeUnit unit); + /** + * Specifies that the task should continue running after waiting for the specified amount, until it is cancelled. + * @param time the time to delay by + * @param unit the unit of time for {@code time} + * @return this builder, for chaining + */ TaskBuilder repeat(int time, TimeUnit unit); + /** + * Clears the delay on this task. + * @return this builder, for chaining + */ TaskBuilder clearDelay(); + /** + * Clears the repeat interval on this task. + * @return this builder, for chaining + */ TaskBuilder clearRepeat(); + /** + * Schedules this task for execution. + * @return the scheduled task + */ ScheduledTask schedule(); } } diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/package-info.java b/api/src/main/java/com/velocitypowered/api/scheduler/package-info.java new file mode 100644 index 000000000..155395528 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/scheduler/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides utilities for scheduling tasks with a fluent builder. + */ +package com.velocitypowered.api.scheduler; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/server/Favicon.java b/api/src/main/java/com/velocitypowered/api/util/Favicon.java similarity index 98% rename from api/src/main/java/com/velocitypowered/api/server/Favicon.java rename to api/src/main/java/com/velocitypowered/api/util/Favicon.java index a25e5bea8..8fc1b2cb3 100644 --- a/api/src/main/java/com/velocitypowered/api/server/Favicon.java +++ b/api/src/main/java/com/velocitypowered/api/util/Favicon.java @@ -1,4 +1,4 @@ -package com.velocitypowered.api.server; +package com.velocitypowered.api.util; import com.google.common.base.Preconditions; import org.checkerframework.checker.nullness.qual.NonNull; diff --git a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java index 237939ef1..5fd716436 100644 --- a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -8,9 +8,9 @@ import java.util.List; import java.util.UUID; /** - * Represents a Mojang game profile. + * Represents a Mojang game profile. This class is immutable. */ -public class GameProfile { +public final class GameProfile { private final String id; private final String name; private final List properties; @@ -37,6 +37,11 @@ public class GameProfile { return properties; } + /** + * Creates a game profile suitable for use in offline-mode. + * @param username the username to use + * @return the new offline-mode game profile + */ public static GameProfile forOfflinePlayer(@NonNull String username) { Preconditions.checkNotNull(username, "username"); String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username)); @@ -52,7 +57,7 @@ public class GameProfile { '}'; } - public class Property { + public final class Property { private final String name; private final String value; private final String signature; diff --git a/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java b/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java index 3cff823cd..a6c89b9cd 100644 --- a/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java +++ b/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java @@ -7,11 +7,19 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.UUID; +/** + * Provides a small, useful selection of utilities for working with Minecraft UUIDs. + */ public class UuidUtils { private UuidUtils() { throw new AssertionError(); } + /** + * Converts from an undashed Mojang-style UUID into a Java {@link UUID} object. + * @param string the string to convert + * @return the UUID object + */ public static @NonNull UUID fromUndashed(final @NonNull String string) { Objects.requireNonNull(string, "string"); Preconditions.checkArgument(string.length() == 32, "Length is incorrect"); @@ -21,11 +29,21 @@ public class UuidUtils { ); } + /** + * Converts from a Java {@link UUID} object into an undashed Mojang-style UUID. + * @param uuid the UUID to convert + * @return the undashed UUID + */ public static @NonNull String toUndashed(final @NonNull UUID uuid) { Preconditions.checkNotNull(uuid, "uuid"); return Long.toUnsignedString(uuid.getMostSignificantBits(), 16) + Long.toUnsignedString(uuid.getLeastSignificantBits(), 16); } + /** + * Generates a UUID for use for offline mode. + * @param username the username to use + * @return the offline mode UUID + */ public static @NonNull UUID generateOfflinePlayerUuid(@NonNull String username) { return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); } diff --git a/api/src/main/java/com/velocitypowered/api/util/package-info.java b/api/src/main/java/com/velocitypowered/api/util/package-info.java new file mode 100644 index 000000000..ef28b6624 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides a selection of miscellaneous utilities for use by plugins and the proxy. + */ +package com.velocitypowered.api.util; \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 76ef64ab1..92db57593 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -10,10 +10,10 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.api.server.Favicon; +import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.plugin.PluginManager; -import com.velocitypowered.api.server.ServerInfo; -import com.velocitypowered.network.ConnectionManager; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.command.ServerCommand; import com.velocitypowered.proxy.command.ShutdownCommand; import com.velocitypowered.proxy.command.VelocityCommand; 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 1a16f34c0..695aa5094 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.VelocityServer; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; 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 45a707b29..5843cb59a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -2,7 +2,7 @@ package com.velocitypowered.proxy.config; import com.google.common.collect.ImmutableMap; import com.moandjiezana.toml.Toml; -import com.velocitypowered.api.server.Favicon; +import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.api.util.LegacyChatColorUtils; import io.netty.buffer.ByteBufUtil; 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 a87c95e40..a6ec95604 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -24,14 +24,14 @@ import javax.crypto.spec.SecretKeySpec; import java.security.GeneralSecurityException; -import static com.velocitypowered.network.Connections.CIPHER_DECODER; -import static com.velocitypowered.network.Connections.CIPHER_ENCODER; -import static com.velocitypowered.network.Connections.COMPRESSION_DECODER; -import static com.velocitypowered.network.Connections.COMPRESSION_ENCODER; -import static com.velocitypowered.network.Connections.FRAME_DECODER; -import static com.velocitypowered.network.Connections.FRAME_ENCODER; -import static com.velocitypowered.network.Connections.MINECRAFT_DECODER; -import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER; +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; /** * A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft 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 46516dd17..23e4f6062 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 @@ -2,7 +2,6 @@ package com.velocitypowered.proxy.connection.backend; import com.google.common.base.Preconditions; 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.proxy.config.PlayerInfoForwarding; @@ -17,7 +16,7 @@ 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.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import io.netty.channel.*; @@ -27,13 +26,13 @@ import io.netty.util.AttributeKey; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import static com.velocitypowered.network.Connections.FRAME_DECODER; -import static com.velocitypowered.network.Connections.FRAME_ENCODER; -import static com.velocitypowered.network.Connections.HANDLER; -import static com.velocitypowered.network.Connections.MINECRAFT_DECODER; -import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER; -import static com.velocitypowered.network.Connections.READ_TIMEOUT; -import static com.velocitypowered.network.Connections.SERVER_READ_TIMEOUT_SECONDS; +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; public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection { static final AttributeKey> CONNECTION_NOTIFIER = 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 91c88ce8b..1660f68b4 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 @@ -21,7 +21,7 @@ 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.util.ThrowableUtils; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; 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 9cfa0227f..58ce7bff5 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 @@ -9,7 +9,7 @@ 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.api.server.ServerPing; +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/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index e4bd26180..67b3cdf82 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 @@ -5,9 +5,9 @@ import com.velocitypowered.api.event.connection.LoginEvent; 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.gameprofile.GameProfileRequestEvent; +import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.proxy.InboundConnection; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.MinecraftPacket; 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 97d479dec..fb70edd02 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 @@ -12,7 +12,7 @@ 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.server.ServerPing; +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/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java similarity index 93% rename from proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java rename to proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index d4efdee5e..732b8b331 100644 --- a/proxy/src/main/java/com/velocitypowered/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -1,4 +1,4 @@ -package com.velocitypowered.network; +package com.velocitypowered.proxy.network; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.velocitypowered.natives.util.Natives; @@ -44,14 +44,7 @@ import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -import static com.velocitypowered.network.Connections.CLIENT_READ_TIMEOUT_SECONDS; -import static com.velocitypowered.network.Connections.FRAME_DECODER; -import static com.velocitypowered.network.Connections.FRAME_ENCODER; -import static com.velocitypowered.network.Connections.LEGACY_PING_DECODER; -import static com.velocitypowered.network.Connections.LEGACY_PING_ENCODER; -import static com.velocitypowered.network.Connections.MINECRAFT_DECODER; -import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER; -import static com.velocitypowered.network.Connections.READ_TIMEOUT; +import static com.velocitypowered.proxy.network.Connections.*; public final class ConnectionManager { private static final Logger logger = LogManager.getLogger(ConnectionManager.class); diff --git a/proxy/src/main/java/com/velocitypowered/network/Connections.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java similarity index 94% rename from proxy/src/main/java/com/velocitypowered/network/Connections.java rename to proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java index 3ca2ff9dc..518a3acbe 100644 --- a/proxy/src/main/java/com/velocitypowered/network/Connections.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java @@ -1,4 +1,4 @@ -package com.velocitypowered.network; +package com.velocitypowered.proxy.network; public interface Connections { String CIPHER_DECODER = "cipher-decoder"; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java index f980ccdb7..2b733f5ef 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java @@ -1,6 +1,6 @@ package com.velocitypowered.proxy.protocol.packet; -import com.velocitypowered.api.server.ServerPing; +import com.velocitypowered.api.proxy.server.ServerPing; import net.kyori.text.serializer.ComponentSerializers; public class LegacyPingResponse { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java index 7243e8b06..012c9fca0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.protocol.util; import com.google.gson.*; -import com.velocitypowered.api.server.Favicon; +import com.velocitypowered.api.util.Favicon; import java.lang.reflect.Type; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java index d961d719d..75784ef0b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java @@ -2,7 +2,7 @@ package com.velocitypowered.proxy.util; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; 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 30d2c617d..e332ed85f 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java @@ -1,6 +1,6 @@ package com.velocitypowered.proxy.util; -import com.velocitypowered.api.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerInfo; import org.junit.jupiter.api.Test; import java.net.InetAddress; From 9033b1c051abfde344847fc20a93a1a75fde21c0 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 25 Aug 2018 00:42:54 -0400 Subject: [PATCH 87/88] Prepare Javadoc deployment --- Jenkinsfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f8f1e9f51..637738676 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent { docker { image 'velocitypowered/openjdk8-plus-git:slim' - args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw' + args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw -v javadoc:/javadoc' } } @@ -23,5 +23,10 @@ pipeline { sh 'export MAVEN_DEPLOYMENT=true; ./gradlew publish' } } + stage('Deploy Javadoc') { + steps { + sh 'rsync -av ./api/build/docs/javadoc/ /javadoc' + } + } } } \ No newline at end of file From 1acf9e1bbb128efe85aff29d832dda17d266991c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 25 Aug 2018 00:44:32 -0400 Subject: [PATCH 88/88] Make sure to clean up old stuff --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 637738676..d7d78b2ec 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,7 +25,7 @@ pipeline { } stage('Deploy Javadoc') { steps { - sh 'rsync -av ./api/build/docs/javadoc/ /javadoc' + sh 'rsync -av --delete ./api/build/docs/javadoc/ /javadoc' } } }