3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 13:30:17 +01:00
Dieser Commit ist enthalten in:
Leymooo 2018-09-20 15:54:22 +03:00
Commit 29b82ae301
80 geänderte Dateien mit 1001 neuen und 728 gelöschten Zeilen

1
.gitignore vendored
Datei anzeigen

@ -135,3 +135,4 @@ logs/
server-icon.png server-icon.png
/bin/ /bin/
run/ run/
plugins/

Datei anzeigen

@ -19,7 +19,8 @@ import javax.tools.StandardLocation;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.*; import java.util.Objects;
import java.util.Set;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"}) @SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor { public class PluginAnnotationProcessor extends AbstractProcessor {

Datei anzeigen

@ -0,0 +1,105 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
/**
* This event is fired when a plugin message is sent to the proxy, either from a client ({@link com.velocitypowered.api.proxy.Player})
* or a server ({@link com.velocitypowered.api.proxy.ServerConnection}).
*/
public class PluginMessageEvent implements ResultedEvent<PluginMessageEvent.ForwardResult> {
private final ChannelMessageSource source;
private final ChannelMessageSink target;
private final ChannelIdentifier identifier;
private final byte[] data;
private ForwardResult result;
public PluginMessageEvent(ChannelMessageSource source, ChannelMessageSink target, ChannelIdentifier identifier, byte[] data) {
this.source = Preconditions.checkNotNull(source, "source");
this.target = Preconditions.checkNotNull(target, "target");
this.identifier = Preconditions.checkNotNull(identifier, "identifier");
this.data = Preconditions.checkNotNull(data, "data");
this.result = ForwardResult.forward();
}
@Override
public ForwardResult getResult() {
return result;
}
@Override
public void setResult(@NonNull ForwardResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public ChannelMessageSource getSource() {
return source;
}
public ChannelMessageSink getTarget() {
return target;
}
public ChannelIdentifier getIdentifier() {
return identifier;
}
public byte[] getData() {
return Arrays.copyOf(data, data.length);
}
public ByteArrayDataInput dataAsDataStream() {
return ByteStreams.newDataInput(data);
}
@Override
public String toString() {
return "PluginMessageEvent{" +
"source=" + source +
", target=" + target +
", identifier=" + identifier +
", data=" + Arrays.toString(data) +
", result=" + result +
'}';
}
/**
* A result determining whether or not to forward this message on.
*/
public static class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true);
private static final ForwardResult DENIED = new ForwardResult(false);
private final boolean allowed;
private ForwardResult(boolean b) {
this.allowed = b;
}
@Override
public boolean isAllowed() {
return allowed;
}
@Override
public String toString() {
return allowed ? "forward to sink" : "handled message at proxy";
}
public static ForwardResult forward() {
return ALLOWED;
}
public static ForwardResult handled() {
return DENIED;
}
}
}

Datei anzeigen

@ -0,0 +1,28 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
/**
* This event is fired once the player has been successfully authenticated and
* fully initialized and player will be connected to server after this event
*/
public class PostLoginEvent {
private final Player player;
public PostLoginEvent(Player player) {
this.player = Preconditions.checkNotNull(player, "player");
}
public Player getPlayer() {
return player;
}
@Override
public String toString() {
return "PostLoginEvent{"
+ "player=" + player
+ '}';
}
}

Datei anzeigen

@ -3,12 +3,13 @@ package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/** /**
* This event is fired when a player has initiated a connection with the proxy but before the proxy authenticates the * 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). * player with Mojang or before the player's proxy connection is fully established (for offline mode).
@ -52,44 +53,59 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
} }
/** /**
* Represents an "allowed/allowed with online mode/denied" result with a reason allowed for denial. * Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial.
*/ */
public static class PreLoginComponentResult extends ResultedEvent.ComponentResult { public static class PreLoginComponentResult implements ResultedEvent.Result {
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
private final boolean onlineMode; private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult(Result.ALLOWED, null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(Result.FORCE_ONLINE, null);
private static final PreLoginComponentResult FORCE_OFFLINEMODE = new PreLoginComponentResult(Result.FORCE_OFFLINE, null);
/** private final Result result;
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode. private final Optional<Component> reason;
* @param allowedOnlineMode if true, online mode will be used for the connection
*/ private PreLoginComponentResult(Result result, @Nullable Component reason) {
private PreLoginComponentResult(boolean allowedOnlineMode) { this.result = result;
super(true, null); this.reason = Optional.ofNullable(reason);
this.onlineMode = allowedOnlineMode;
} }
private PreLoginComponentResult(@Nullable Component reason) { @Override
super(reason == null, reason); public boolean isAllowed() {
// Don't care about this return result != Result.DISALLOWED;
this.onlineMode = false; }
public Optional<Component> getReason() {
return reason;
} }
public boolean isOnlineModeAllowed() { public boolean isOnlineModeAllowed() {
return this.onlineMode; return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
} }
@Override @Override
public String toString() { public String toString() {
if (isForceOfflineMode()) {
return "allowed with force offline mode";
}
if (isOnlineModeAllowed()) { if (isOnlineModeAllowed()) {
return "allowed with online mode"; return "allowed with online mode";
} }
if (isAllowed()) {
return super.toString(); return "allowed";
}
if (reason.isPresent()) {
return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get());
}
return "denied";
} }
/** /**
* Returns a result indicating the connection will be allowed through the proxy. * Returns a result indicating the connection will be allowed through
* the proxy.
* @return the allowed result * @return the allowed result
*/ */
public static PreLoginComponentResult allowed() { public static PreLoginComponentResult allowed() {
@ -97,23 +113,41 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
} }
/** /**
* Returns a result indicating the connection will be allowed through the proxy, but the connection will be * Returns a result indicating the connection will be allowed through
* forced to use online mode provided that the proxy is in offline mode. This acts similarly to {@link #allowed()} * the proxy, but the connection will be forced to use online mode
* on an online-mode proxy. * provided that the proxy is in offline mode. This acts similarly to
* {@link #allowed()} on an online-mode proxy.
* @return the result * @return the result
*/ */
public static PreLoginComponentResult forceOnlineMode() { public static PreLoginComponentResult forceOnlineMode() {
return FORCE_ONLINEMODE; return FORCE_ONLINEMODE;
} }
/**
* Returns a result indicating the connection will be allowed through
* the proxy, but the connection will be forced to use offline mode even
* when proxy running in online mode
* @return the result
*/
public static PreLoginComponentResult forceOfflineMode() {
return FORCE_OFFLINEMODE;
}
/** /**
* Denies the login with the specified reason. * Denies the login with the specified reason.
* @param reason the reason for disallowing the connection * @param reason the reason for disallowing the connection
* @return a new result * @return a new result
*/ */
public static PreLoginComponentResult denied(@NonNull Component reason) { public static PreLoginComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "reason"); Preconditions.checkNotNull(reason, "reason");
return new PreLoginComponentResult(reason); return new PreLoginComponentResult(Result.DISALLOWED, reason);
}
private enum Result {
ALLOWED,
FORCE_ONLINE,
FORCE_OFFLINE,
DISALLOWED
} }
} }
} }

Datei anzeigen

@ -1,10 +1,9 @@
package com.velocitypowered.api.event.player; package com.velocitypowered.api.event.player;
import com.velocitypowered.api.proxy.InboundConnection;
import org.checkerframework.checker.nullness.qual.Nullable;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in order to set up the * This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in order to set up the

Datei anzeigen

@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@ -13,12 +13,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*/ */
public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> { public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> {
private final Player player; private final Player player;
private final ServerInfo server; private final RegisteredServer server;
private final Component originalReason; private final Component originalReason;
private final boolean duringLogin; private final boolean duringLogin;
private ServerKickResult result; private ServerKickResult result;
public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) { public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) {
this.player = Preconditions.checkNotNull(player, "player"); this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason"); this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return player; return player;
} }
public ServerInfo getServer() { public RegisteredServer getServer() {
return server; return server;
} }
@ -91,9 +91,9 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* when this result is used. * when this result is used.
*/ */
public static class RedirectPlayer implements ServerKickResult { public static class RedirectPlayer implements ServerKickResult {
private final ServerInfo server; private final RegisteredServer server;
private RedirectPlayer(ServerInfo server) { private RedirectPlayer(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
} }
@ -102,7 +102,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return false; return false;
} }
public ServerInfo getServer() { public RegisteredServer getServer() {
return server; return server;
} }
@ -111,7 +111,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* @param server the server to send the player to * @param server the server to send the player to
* @return the redirect result * @return the redirect result
*/ */
public static RedirectPlayer create(ServerInfo server) { public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server); return new RedirectPlayer(server);
} }
} }

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.api.event.player; package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.PlayerSettings;
public class PlayerSettingsChangedEvent { public class PlayerSettingsChangedEvent {
private final Player player; private final Player player;

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.RegisteredServer;
/** /**
* This event is fired once the player has successfully connected to the target server and the connection to the previous * This event is fired once the player has successfully connected to the target server and the connection to the previous
@ -10,9 +10,9 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
*/ */
public class ServerConnectedEvent { public class ServerConnectedEvent {
private final Player player; private final Player player;
private final ServerInfo server; private final RegisteredServer server;
public ServerConnectedEvent(Player player, ServerInfo server) { public ServerConnectedEvent(Player player, RegisteredServer server) {
this.player = Preconditions.checkNotNull(player, "player"); this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
} }
@ -21,7 +21,7 @@ public class ServerConnectedEvent {
return player; return player;
} }
public ServerInfo getServer() { public RegisteredServer getServer() {
return server; return server;
} }

Datei anzeigen

@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.RegisteredServer;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -14,11 +14,13 @@ import java.util.Optional;
*/ */
public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> { public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> {
private final Player player; private final Player player;
private final RegisteredServer originalServer;
private ServerResult result; private ServerResult result;
public ServerPreConnectEvent(Player player, ServerResult result) { public ServerPreConnectEvent(Player player, RegisteredServer originalServer) {
this.player = Preconditions.checkNotNull(player, "player"); this.player = Preconditions.checkNotNull(player, "player");
this.result = Preconditions.checkNotNull(result, "result"); this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
this.result = ServerResult.allowed(originalServer);
} }
public Player getPlayer() { public Player getPlayer() {
@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
this.result = Preconditions.checkNotNull(result, "result"); this.result = Preconditions.checkNotNull(result, "result");
} }
public RegisteredServer getOriginalServer() {
return originalServer;
}
@Override @Override
public String toString() { public String toString() {
return "ServerPreConnectEvent{" + return "ServerPreConnectEvent{" +
"player=" + player + "player=" + player +
", originalServer=" + originalServer +
", result=" + result + ", result=" + result +
'}'; '}';
} }
@ -50,11 +57,11 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
private static final ServerResult DENIED = new ServerResult(false, null); private static final ServerResult DENIED = new ServerResult(false, null);
private final boolean allowed; private final boolean allowed;
private final ServerInfo info; private final RegisteredServer server;
private ServerResult(boolean allowed, @Nullable ServerInfo info) { private ServerResult(boolean allowed, @Nullable RegisteredServer server) {
this.allowed = allowed; this.allowed = allowed;
this.info = info; this.server = server;
} }
@Override @Override
@ -62,8 +69,8 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
return allowed; return allowed;
} }
public Optional<ServerInfo> getInfo() { public Optional<RegisteredServer> getServer() {
return Optional.ofNullable(info); return Optional.ofNullable(server);
} }
@Override @Override
@ -71,14 +78,14 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
if (!allowed) { if (!allowed) {
return "denied"; return "denied";
} }
return "allowed: connect to " + info.getName(); return "allowed: connect to " + server.getServerInfo().getName();
} }
public static ServerResult denied() { public static ServerResult denied() {
return DENIED; return DENIED;
} }
public static ServerResult allowed(ServerInfo server) { public static ServerResult allowed(RegisteredServer server) {
Preconditions.checkNotNull(server, "server"); Preconditions.checkNotNull(server, "server");
return new ServerResult(true, server); return new ServerResult(true, server);
} }

Datei anzeigen

@ -1,7 +1,6 @@
package com.velocitypowered.api.plugin.meta; package com.velocitypowered.api.plugin.meta;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;

Datei anzeigen

@ -1,29 +1,28 @@
package com.velocitypowered.api.proxy; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection * 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)}. * request is created using {@link Player#createConnectionRequest(RegisteredServer)}.
*/ */
public interface ConnectionRequestBuilder { public interface ConnectionRequestBuilder {
/** /**
* Returns the server that this connection request represents. * Returns the server that this connection request represents.
* @return the server this request will connect to * @return the server this request will connect to
*/ */
@NonNull ServerInfo getServer(); RegisteredServer getServer();
/** /**
* Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user * 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. * 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 * @return a {@link CompletableFuture} representing the status of this connection
*/ */
@NonNull CompletableFuture<Result> connect(); CompletableFuture<Result> connect();
/** /**
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error * Initiates the connection to the remote server without waiting for a result. Velocity will use generic error

Datei anzeigen

@ -1,11 +1,11 @@
package com.velocitypowered.api.proxy; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.MessagePosition;
import java.util.List; import java.util.List;
import net.kyori.text.Component; import net.kyori.text.Component;
@ -65,10 +65,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
/** /**
* Creates a new connection request so that the player can connect to another server. * Creates a new connection request so that the player can connect to another server.
* @param info the server to connect to * @param server the server to connect to
* @return a new connection request * @return a new connection request
*/ */
ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server);
/** /**
* Gets a game profile properties of player * Gets a game profile properties of player

Datei anzeigen

@ -1,12 +1,13 @@
package com.velocitypowered.api.proxy; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar; import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.Scheduler;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
@ -45,23 +46,24 @@ public interface ProxyServer {
int getPlayerCount(); int getPlayerCount();
/** /**
* Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive. * Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive.
* @param name the name of the server * @param name the name of the server
* @return the registered server, which may be empty * @return the registered server, which may be empty
*/ */
Optional<ServerInfo> getServerInfo(String name); Optional<RegisteredServer> getServer(String name);
/** /**
* Retrieves all {@link ServerInfo}s registered with this proxy. * Retrieves all {@link RegisteredServer}s registered with this proxy.
* @return the servers registered with this proxy * @return the servers registered with this proxy
*/ */
Collection<ServerInfo> getAllServers(); Collection<RegisteredServer> getAllServers();
/** /**
* Registers a server with this proxy. A server with this name should not already exist. * Registers a server with this proxy. A server with this name should not already exist.
* @param server the server to register * @param server the server to register
* @return the newly registered server
*/ */
void registerServer(ServerInfo server); RegisteredServer registerServer(ServerInfo server);
/** /**
* Unregisters this server from the proxy. * Unregisters this server from the proxy.

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
/** /**
@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi
* Returns the server that this connection is connected to. * Returns the server that this connection is connected to.
* @return the server this connection is connected to * @return the server this connection is connected to
*/ */
RegisteredServer getServer();
/**
* Returns the server info for this connection.
* @return the server info for this connection
*/
ServerInfo getServerInfo(); ServerInfo getServerInfo();
/** /**

Datei anzeigen

@ -8,6 +8,7 @@ public interface ChannelMessageSink {
* Sends a plugin message to this target. * Sends a plugin message to this target.
* @param identifier the channel identifier to send the message on * @param identifier the channel identifier to send the message on
* @param data the data to send * @param data the data to send
* @return whether or not the message could be sent
*/ */
void sendPluginMessage(ChannelIdentifier identifier, byte[] data); boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data);
} }

Datei anzeigen

@ -1,16 +1,14 @@
package com.velocitypowered.api.proxy.messages; package com.velocitypowered.api.proxy.messages;
/** /**
* Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from * Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on.
* the client or the server.
*/ */
public interface ChannelRegistrar { public interface ChannelRegistrar {
/** /**
* Registers the specified message handler to listen for plugin messages on the specified channels. * Registers the specified message identifiers to listen on for the
* @param handler the handler to register
* @param identifiers the channel identifiers to register * @param identifiers the channel identifiers to register
*/ */
void register(MessageHandler handler, ChannelIdentifier... identifiers); void register(ChannelIdentifier... identifiers);
/** /**
* Unregisters the handler for the specified channel. * Unregisters the handler for the specified channel.

Datei anzeigen

@ -1,15 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents from "which side" of the proxy the plugin message came from.
*/
public enum ChannelSide {
/**
* The plugin message came from a server that a client was connected to.
*/
FROM_SERVER,
/**
* The plugin message came from the client.
*/
FROM_CLIENT
}

Datei anzeigen

@ -1,28 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents a handler for handling plugin messages.
*/
public interface MessageHandler {
/**
* Handles an incoming plugin message.
* @param source the source of the plugin message
* @param side from where the plugin message originated
* @param identifier the channel on which the message was sent
* @param data the data inside the plugin message
* @return a {@link ForwardStatus} indicating whether or not to forward this plugin message on
*/
ForwardStatus handle(ChannelMessageSource source, ChannelSide side, ChannelIdentifier identifier, byte[] data);
enum ForwardStatus {
/**
* Forwards this plugin message on to the client or server, depending on the {@link ChannelSide} it originated
* from.
*/
FORWARD,
/**
* Discard the plugin message and do not forward it on.
*/
HANDLED
}
}

Datei anzeigen

@ -10,7 +10,7 @@ import java.util.regex.Pattern;
* Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use. * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use.
*/ */
public final class MinecraftChannelIdentifier implements ChannelIdentifier { public final class MinecraftChannelIdentifier implements ChannelIdentifier {
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+", Pattern.CASE_INSENSITIVE); private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
private final String namespace; private final String namespace;
private final String name; private final String name;

Datei anzeigen

@ -0,0 +1,30 @@
package com.velocitypowered.api.proxy.server;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
/**
* Represents a server that has been registered with the proxy.
*/
public interface RegisteredServer extends ChannelMessageSink {
/**
* Returns the {@link ServerInfo} for this server.
* @return the server info
*/
ServerInfo getServerInfo();
/**
* Returns a list of all the players currently connected to this server on this proxy.
* @return the players on this proxy
*/
Collection<Player> getPlayersConnected();
/**
* Attempts to ping the remote server and return the server list ping result.
* @return the server ping result from the server
*/
CompletableFuture<ServerPing> ping();
}

Datei anzeigen

@ -74,6 +74,7 @@ public class ServerPing {
builder.favicon = favicon; builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null; builder.nullOutModinfo = modinfo == null;
if (modinfo != null) { if (modinfo != null) {
builder.modType = modinfo.type;
builder.mods.addAll(modinfo.modList); builder.mods.addAll(modinfo.modList);
} }
return builder; return builder;
@ -91,6 +92,7 @@ public class ServerPing {
private int onlinePlayers; private int onlinePlayers;
private int maximumPlayers; private int maximumPlayers;
private final List<SamplePlayer> samplePlayers = new ArrayList<>(); private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType;
private final List<Mod> mods = new ArrayList<>(); private final List<Mod> mods = new ArrayList<>();
private Component description; private Component description;
private Favicon favicon; private Favicon favicon;
@ -121,6 +123,11 @@ public class ServerPing {
return this; return this;
} }
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(Mod... mods) { public Builder mods(Mod... mods) {
this.mods.addAll(Arrays.asList(mods)); this.mods.addAll(Arrays.asList(mods));
return this; return this;
@ -158,7 +165,7 @@ public class ServerPing {
public ServerPing build() { public ServerPing build() {
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon, return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon,
nullOutModinfo ? null : new Modinfo(mods)); nullOutModinfo ? null : new Modinfo(modType, mods));
} }
public Version getVersion() { public Version getVersion() {
@ -185,6 +192,10 @@ public class ServerPing {
return favicon; return favicon;
} }
public String getModType() {
return modType;
}
public List<Mod> getMods() { public List<Mod> getMods() {
return mods; return mods;
} }
@ -196,6 +207,7 @@ public class ServerPing {
", onlinePlayers=" + onlinePlayers + ", onlinePlayers=" + onlinePlayers +
", maximumPlayers=" + maximumPlayers + ", maximumPlayers=" + maximumPlayers +
", samplePlayers=" + samplePlayers + ", samplePlayers=" + samplePlayers +
", modType=" + modType +
", mods=" + mods + ", mods=" + mods +
", description=" + description + ", description=" + description +
", favicon=" + favicon + ", favicon=" + favicon +
@ -291,14 +303,23 @@ public class ServerPing {
} }
public static class Modinfo { public static class Modinfo {
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of()); public static final Modinfo DEFAULT = new Modinfo("FML", ImmutableList.of());
private final String type = "FML"; private final String type;
private final List<Mod> modList; private final List<Mod> modList;
public Modinfo(List<Mod> modList) { public Modinfo(String type, List<Mod> modList) {
this.type = Preconditions.checkNotNull(type, "type");
this.modList = ImmutableList.copyOf(modList); this.modList = ImmutableList.copyOf(modList);
} }
public String getType() {
return type;
}
public List<Mod> getMods() {
return modList;
}
} }
public static class Mod { public static class Mod {

Datei anzeigen

@ -24,7 +24,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time} * @param unit the unit of time for {@code time}
* @return this builder, for chaining * @return this builder, for chaining
*/ */
TaskBuilder delay(int time, TimeUnit unit); TaskBuilder delay(long time, TimeUnit unit);
/** /**
* Specifies that the task should continue running after waiting for the specified amount, until it is cancelled. * Specifies that the task should continue running after waiting for the specified amount, until it is cancelled.
@ -32,7 +32,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time} * @param unit the unit of time for {@code time}
* @return this builder, for chaining * @return this builder, for chaining
*/ */
TaskBuilder repeat(int time, TimeUnit unit); TaskBuilder repeat(long time, TimeUnit unit);
/** /**
* Clears the delay on this task. * Clears the delay on this task.

Datei anzeigen

@ -1,66 +0,0 @@
package com.velocitypowered.api.util;
import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.regex.Pattern;
/**
* LegacyChatColorUtils contains utilities for handling legacy Minecraft color codes. Generally, you should prefer
* JSON-based components, but for convenience Velocity provides a limited set of tools to handle Minecraft color codes.
*/
public class LegacyChatColorUtils {
private LegacyChatColorUtils() {
throw new AssertionError();
}
/**
* Represents the legacy Minecraft format character, the section symbol.
*/
public static final char FORMAT_CHAR = '\u00a7';
/**
* Translates a string with Minecraft color codes prefixed with a different character than the section symbol into
* a string that uses the section symbol.
* @param originalChar the char the color codes are prefixed by
* @param text the text to translate
* @return the translated text
*/
public static String translate(char originalChar, @NonNull String text) {
Preconditions.checkNotNull(text, "text");
char[] textChars = text.toCharArray();
int foundSectionIdx = -1;
for (int i = 0; i < textChars.length; i++) {
char textChar = textChars[i];
if (textChar == originalChar) {
foundSectionIdx = i;
continue;
}
if (foundSectionIdx >= 0) {
textChar = Character.toLowerCase(textChar);
if ((textChar >= 'a' && textChar <= 'f') || (textChar >= '0' && textChar <= '9') ||
(textChar >= 'l' && textChar <= 'o' || textChar == 'r')) {
textChars[foundSectionIdx] = FORMAT_CHAR;
}
foundSectionIdx = -1;
}
}
return new String(textChars);
}
/**
* A regex that matches all Minecraft color codes and removes them.
*/
private static final Pattern CHAT_COLOR_MATCHER = Pattern.compile("(?i)" + Character.toString(FORMAT_CHAR) + "[0-9A-FL-OR]");
/**
* Removes all Minecraft color codes from the string.
* @param text the text to remove color codes from
* @return a new String without Minecraft color codes
*/
public static String removeFormatting(@NonNull String text) {
Preconditions.checkNotNull(text, "text");
return CHAT_COLOR_MATCHER.matcher(text).replaceAll("");
}
}

Datei anzeigen

@ -1,62 +0,0 @@
package com.velocitypowered.api.util;
import com.velocitypowered.api.util.LegacyChatColorUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LegacyChatColorUtilsTest {
private static final String NON_FORMATTED = "Velocity";
private static final String FORMATTED = "\u00a7cVelocity";
private static final String FORMATTED_MULTIPLE = "\u00a7c\u00a7lVelocity";
private static final String FORMATTED_MULTIPLE_VARIED = "\u00a7c\u00a7lVelo\u00a7a\u00a7mcity";
private static final String INVALID = "\u00a7gVelocity";
private static final String RAW_SECTION = "\u00a7";
@Test
void removeFormattingNonFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(NON_FORMATTED));
}
@Test
void removeFormattingFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED));
}
@Test
void removeFormattingFormattedMultiple() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE));
}
@Test
void removeFormattingFormattedMultipleVaried() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE_VARIED));
}
@Test
void removeFormattingInvalidFormat() {
assertEquals(INVALID, LegacyChatColorUtils.removeFormatting(INVALID));
}
@Test
void removeFormattingRawSection() {
assertEquals(RAW_SECTION, LegacyChatColorUtils.removeFormatting(RAW_SECTION));
}
@Test
void translate() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('&', "&cVelocity"));
}
@Test
void translateMultiple() {
assertEquals(FORMATTED_MULTIPLE, LegacyChatColorUtils.translate('&', "&c&lVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('&', "&c&lVelo&a&mcity"));
}
@Test
void translateDifferentChar() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('$', "$cVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('$', "$c$lVelo$a$mcity"));
}
}

Datei anzeigen

@ -8,7 +8,7 @@ import java.util.function.Supplier;
public class NativeCodeLoader<T> implements Supplier<T> { public class NativeCodeLoader<T> implements Supplier<T> {
private final List<Variant<T>> variants; private final List<Variant<T>> variants;
private Variant<T> selected; private volatile Variant<T> selected;
public NativeCodeLoader(List<Variant<T>> variants) { public NativeCodeLoader(List<Variant<T>> variants) {
this.variants = ImmutableList.copyOf(variants); this.variants = ImmutableList.copyOf(variants);
@ -16,36 +16,41 @@ public class NativeCodeLoader<T> implements Supplier<T> {
@Override @Override
public T get() { public T get() {
if (selected == null) { return tryLoad().object;
selected = select(); }
}
return selected.object; private Variant<T> tryLoad() {
if (selected != null) {
return selected;
}
synchronized (this) {
if (selected != null) {
return selected;
} }
private Variant<T> select() {
for (Variant<T> variant : variants) { for (Variant<T> variant : variants) {
T got = variant.get(); T got = variant.get();
if (got == null) { if (got == null) {
continue; continue;
} }
return variant; selected = variant;
return selected;
} }
throw new IllegalArgumentException("Can't find any suitable variants"); throw new IllegalArgumentException("Can't find any suitable variants");
} }
}
public String getLoadedVariant() { public String getLoadedVariant() {
if (selected == null) { return tryLoad().name;
selected = select();
}
return selected.name;
} }
static class Variant<T> { static class Variant<T> {
private boolean available; private volatile boolean available;
private final Runnable setup; private final Runnable setup;
private final String name; private final String name;
private final T object; private final T object;
private boolean hasBeenSetup = false; private volatile boolean hasBeenSetup = false;
Variant(BooleanSupplier available, Runnable setup, String name, T object) { Variant(BooleanSupplier available, Runnable setup, String name, T object) {
this.available = available.getAsBoolean(); this.available = available.getAsBoolean();
@ -54,29 +59,36 @@ public class NativeCodeLoader<T> implements Supplier<T> {
this.object = object; this.object = object;
} }
private void setup() { public T get() {
if (available && !hasBeenSetup) { if (!available) {
return null;
}
// Make sure setup happens only once
if (!hasBeenSetup) {
synchronized (this) {
// We change availability if need be below, may as well check it again here for sanity.
if (!available) {
return null;
}
// Okay, now try the setup if we haven't done so yet.
if (!hasBeenSetup) {
try { try {
setup.run(); setup.run();
hasBeenSetup = true; hasBeenSetup = true;
return object;
} catch (Exception e) { } catch (Exception e) {
available = false; available = false;
}
}
}
public T get() {
if (!hasBeenSetup) {
setup();
}
if (available) {
return object;
}
return null; return null;
} }
} }
}
}
return object;
}
}
static final BooleanSupplier MACOS = () -> System.getProperty("os.name").equalsIgnoreCase("Mac OS X") && static final BooleanSupplier MACOS = () -> System.getProperty("os.name").equalsIgnoreCase("Mac OS X") &&
System.getProperty("os.arch").equals("x86_64"); System.getProperty("os.arch").equals("x86_64");

Datei anzeigen

@ -3,10 +3,8 @@ package com.velocitypowered.natives.util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.natives.compression.JavaVelocityCompressor; import com.velocitypowered.natives.compression.JavaVelocityCompressor;
import com.velocitypowered.natives.compression.NativeVelocityCompressor; import com.velocitypowered.natives.compression.NativeVelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.compression.VelocityCompressorFactory;
import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import java.io.IOException; import java.io.IOException;

Datei anzeigen

@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs;
import java.util.Random; import java.util.Random;
import java.util.function.Supplier;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;

Datei anzeigen

@ -8,29 +8,30 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.command.ServerCommand; import com.velocitypowered.proxy.command.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand; import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand; import com.velocitypowered.proxy.command.VelocityCommand;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.command.VelocityCommandManager; import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.config.AnnotatedConfig; import com.velocitypowered.proxy.config.AnnotatedConfig;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.messages.VelocityChannelRegistrar; import com.velocitypowered.proxy.messages.VelocityChannelRegistrar;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.plugin.VelocityEventManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.plugin.VelocityPluginManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.scheduler.VelocityScheduler; import com.velocitypowered.proxy.scheduler.VelocityScheduler;
import com.velocitypowered.proxy.server.ServerMap;
import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.Ratelimiter; import com.velocitypowered.proxy.util.Ratelimiter;
import com.velocitypowered.proxy.util.ServerMap;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -61,7 +62,7 @@ public class VelocityServer implements ProxyServer {
private VelocityConfiguration configuration; private VelocityConfiguration configuration;
private NettyHttpClient httpClient; private NettyHttpClient httpClient;
private KeyPair serverKeyPair; private KeyPair serverKeyPair;
private final ServerMap servers = new ServerMap(); private final ServerMap servers = new ServerMap(this);
private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = false; private boolean shutdown = false;
@ -263,19 +264,19 @@ public class VelocityServer implements ProxyServer {
} }
@Override @Override
public Optional<ServerInfo> getServerInfo(String name) { public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "name"); Preconditions.checkNotNull(name, "name");
return servers.getServer(name); return servers.getServer(name);
} }
@Override @Override
public Collection<ServerInfo> getAllServers() { public Collection<RegisteredServer> getAllServers() {
return servers.getAllServers(); return servers.getAllServers();
} }
@Override @Override
public void registerServer(ServerInfo server) { public RegisteredServer registerServer(ServerInfo server) {
servers.register(server); return servers.register(server);
} }
@Override @Override

Datei anzeigen

@ -6,6 +6,7 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent; import net.kyori.text.event.ClickEvent;
@ -34,7 +35,7 @@ public class ServerCommand implements Command {
if (args.length == 1) { if (args.length == 1) {
// Trying to connect to a server. // Trying to connect to a server.
String serverName = args[0]; String serverName = args[0];
Optional<ServerInfo> toConnect = server.getServerInfo(serverName); Optional<RegisteredServer> toConnect = server.getServer(serverName);
if (!toConnect.isPresent()) { if (!toConnect.isPresent()) {
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
return; return;
@ -48,17 +49,19 @@ public class ServerCommand implements Command {
// Assemble the list of servers as components // Assemble the list of servers as components
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW); TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
List<ServerInfo> infos = ImmutableList.copyOf(server.getAllServers()); List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
for (int i = 0; i < infos.size(); i++) { for (int i = 0; i < infos.size(); i++) {
ServerInfo serverInfo = infos.get(i); RegisteredServer rs = infos.get(i);
TextComponent infoComponent = TextComponent.of(serverInfo.getName()); TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
if (serverInfo.getName().equals(currentServer)) { String playersText = rs.getPlayersConnected().size() + " player(s) online";
if (rs.getServerInfo().getName().equals(currentServer)) {
infoComponent = infoComponent.color(TextColor.GREEN) infoComponent = infoComponent.color(TextColor.GREEN)
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Currently connected to this server"))); .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
TextComponent.of("Currently connected to this server\n" + playersText)));
} else { } else {
infoComponent = infoComponent.color(TextColor.GRAY) infoComponent = infoComponent.color(TextColor.GRAY)
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName())) .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName()))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server"))); .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
} }
serverListBuilder.append(infoComponent); serverListBuilder.append(infoComponent);
if (i != infos.size() - 1) { if (i != infos.size() - 1) {
@ -74,11 +77,11 @@ public class ServerCommand implements Command {
public List<String> suggest(CommandSource source, String[] currentArgs) { public List<String> suggest(CommandSource source, String[] currentArgs) {
if (currentArgs.length == 0) { if (currentArgs.length == 0) {
return server.getAllServers().stream() return server.getAllServers().stream()
.map(ServerInfo::getName) .map(rs -> rs.getServerInfo().getName())
.collect(Collectors.toList()); .collect(Collectors.toList());
} else if (currentArgs.length == 1) { } else if (currentArgs.length == 1) {
return server.getAllServers().stream() return server.getAllServers().stream()
.map(ServerInfo::getName) .map(rs -> rs.getServerInfo().getName())
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} else { } else {

Datei anzeigen

@ -1,10 +1,9 @@
package com.velocitypowered.proxy.command; package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;

Datei anzeigen

@ -1,5 +1,8 @@
package com.velocitypowered.proxy.config; package com.velocitypowered.proxy.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Only for simple configs * Only for simple configs

Datei anzeigen

@ -5,10 +5,9 @@ import com.google.common.collect.ImmutableMap;
import com.moandjiezana.toml.Toml; import com.moandjiezana.toml.Toml;
import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.util.AddressUtil; 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.Component;
import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.ComponentSerializers;
import org.apache.logging.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
@ -17,12 +16,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.logging.log4j.Logger;
public class VelocityConfiguration extends AnnotatedConfig { public class VelocityConfiguration extends AnnotatedConfig {
@ -273,6 +267,14 @@ public class VelocityConfiguration extends AnnotatedConfig {
return announceForge; return announceForge;
} }
public int getConnectTimeout() {
return advanced.getConnectionTimeout();
}
public int getReadTimeout() {
return advanced.getReadTimeout();
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@ -420,21 +422,23 @@ public class VelocityConfiguration extends AnnotatedConfig {
"Disable by setting to 0"}) "Disable by setting to 0"})
@ConfigKey("login-ratelimit") @ConfigKey("login-ratelimit")
private int loginRatelimit = 3000; private int loginRatelimit = 3000;
@Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."})
@ConfigKey("connection-timeout")
private int connectionTimeout = 5000;
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
@ConfigKey("read-timeout")
private int readTimeout = 30000;
private Advanced() { private Advanced() {
} }
private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) {
this.compressionThreshold = compressionThreshold;
this.compressionLevel = compressionLevel;
this.loginRatelimit = loginRatelimit;
}
private Advanced(Toml toml) { private Advanced(Toml toml) {
if (toml != null) { if (toml != null) {
this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue();
this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
} }
} }
@ -442,33 +446,31 @@ public class VelocityConfiguration extends AnnotatedConfig {
return compressionThreshold; return compressionThreshold;
} }
public void setCompressionThreshold(int compressionThreshold) {
this.compressionThreshold = compressionThreshold;
}
public int getCompressionLevel() { public int getCompressionLevel() {
return compressionLevel; return compressionLevel;
} }
public void setCompressionLevel(int compressionLevel) {
this.compressionLevel = compressionLevel;
}
public int getLoginRatelimit() { public int getLoginRatelimit() {
return loginRatelimit; return loginRatelimit;
} }
public void setLoginRatelimit(int loginRatelimit) { public int getConnectionTimeout() {
this.loginRatelimit = loginRatelimit; return connectionTimeout;
}
public int getReadTimeout() {
return readTimeout;
} }
@Override @Override
public String toString() { public String toString() {
return "Advanced{" return "Advanced{" +
+ "compressionThreshold=" + compressionThreshold "compressionThreshold=" + compressionThreshold +
+ ", compressionLevel=" + compressionLevel ", compressionLevel=" + compressionLevel +
+ ", loginRatelimit=" + loginRatelimit ", loginRatelimit=" + loginRatelimit +
+ '}'; ", connectionTimeout=" + connectionTimeout +
", readTimeout=" + readTimeout +
'}';
} }
} }
@ -500,18 +502,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
return queryEnabled; return queryEnabled;
} }
public void setQueryEnabled(boolean queryEnabled) {
this.queryEnabled = queryEnabled;
}
public int getQueryPort() { public int getQueryPort() {
return queryPort; return queryPort;
} }
public void setQueryPort(int queryPort) {
this.queryPort = queryPort;
}
@Override @Override
public String toString() { public String toString() {
return "Query{" return "Query{"

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.proxy.protocol.netty.*; import com.velocitypowered.proxy.protocol.netty.*;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER; import static com.velocitypowered.proxy.network.Connections.*;
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 * A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
@ -99,8 +91,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (association != null) { if (association != null) {
logger.error("{}: exception encountered", association, cause); logger.error("{}: exception encountered", association, cause);
} else {
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
} }
ctx.close(); ctx.close();

Datei anzeigen

@ -8,6 +8,8 @@ public class VelocityConstants {
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS";
public static final String FORGE_LEGACY_CHANNEL = "FML";
public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP";
public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 }; public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
} }

Datei anzeigen

@ -1,15 +1,16 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MessageHandler;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { public void activated() {
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo())); server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer()));
connection.getServer().addPlayer(connection.getPlayer());
} }
@Override @Override
@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) { if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors. // errors.
connection.getMinecraftConnection().close(); connection.disconnect();
return; return;
} }
@ -44,7 +46,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
connection.getPlayer().getConnection().write(packet); connection.getPlayer().getConnection().write(packet);
} else if (packet instanceof Disconnect) { } else if (packet instanceof Disconnect) {
Disconnect original = (Disconnect) packet; Disconnect original = (Disconnect) packet;
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original); connection.disconnect();
connection.getPlayer().handleConnectionException(connection.getServer(), original);
} else if (packet instanceof JoinGame) { } else if (packet instanceof JoinGame) {
playerHandler.handleBackendJoinGame((JoinGame) packet); playerHandler.handleBackendJoinGame((JoinGame) packet);
} else if (packet instanceof BossBar) { } else if (packet instanceof BossBar) {
@ -83,10 +86,17 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return; return;
} }
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection, ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel());
ChannelSide.FROM_SERVER, pm); if (id == null) {
if (status == MessageHandler.ForwardStatus.FORWARD) {
connection.getPlayer().getConnection().write(pm); connection.getPlayer().getConnection().write(pm);
} else {
PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), id, pm.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
connection.getPlayer().getConnection().write(pm);
}
}, connection.getMinecraftConnection().getChannel().eventLoop());
} }
} else if (connection.hasCompletedJoin()) { } else if (connection.hasCompletedJoin()) {
// Just forward the packet on. We don't have anything to handle at this time. // Just forward the packet on. We don't have anything to handle at this time.
@ -99,7 +109,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) { if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors. // errors.
connection.getMinecraftConnection().close(); connection.disconnect();
return; return;
} }
@ -110,7 +120,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void exception(Throwable throwable) { public void exception(Throwable throwable) {
connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable); connection.getPlayer().handleConnectionException(connection.getServer(), throwable);
}
public VelocityServer getServer() {
return server;
}
@Override
public void disconnected() {
connection.getServer().removePlayer(connection.getPlayer());
if (!connection.isGracefulDisconnect()) {
connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create(
ConnectionMessages.UNEXPECTED_DISCONNECT));
}
} }
private boolean canForwardPluginMessage(PluginMessage message) { private boolean canForwardPluginMessage(PluginMessage message) {

Datei anzeigen

@ -1,18 +1,18 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
if (existingConnection == null) { if (existingConnection == null) {
// Strap on the play session handler // Strap on the play session handler
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer())); connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer()));
// This is for legacy Forge servers - during first connection the FML handshake will transition to complete regardless
// Thus, we need to ensure that a reset packet is ALWAYS sent on first switch.
//
// The call will handle if the player is not a Forge player appropriately.
connection.getPlayer().getConnection().setCanSendLegacyFMLResetPacket(true);
} else { } else {
// The previous server connection should become obsolete. // The previous server connection should become obsolete.
// Before we remove it, if the server we are departing is modded, we must always reset the client state. // Before we remove it, if the server we are departing is modded, we must always reset the client state.

Datei anzeigen

@ -4,9 +4,14 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.protocol.StateRegistry; import io.netty.channel.Channel;
import com.velocitypowered.api.proxy.server.ServerInfo; import io.netty.channel.ChannelFuture;
import com.velocitypowered.proxy.VelocityServer; import io.netty.channel.ChannelFutureListener;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.*;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
@ -27,27 +31,22 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.velocitypowered.proxy.VelocityServer.GSON; import static com.velocitypowered.proxy.VelocityServer.GSON;
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; import static com.velocitypowered.proxy.network.Connections.*;
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 { public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER = static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
AttributeKey.newInstance("connection-notification-result"); AttributeKey.newInstance("connection-notification-result");
private final ServerInfo serverInfo; private final VelocityRegisteredServer registeredServer;
private final ConnectedPlayer proxyPlayer; private final ConnectedPlayer proxyPlayer;
private final VelocityServer server; private final VelocityServer server;
private MinecraftConnection minecraftConnection; private MinecraftConnection minecraftConnection;
private boolean legacyForge = false; private boolean legacyForge = false;
private boolean hasCompletedJoin = false; private boolean hasCompletedJoin = false;
private boolean gracefulDisconnect = false;
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.serverInfo = target; this.registeredServer = registeredServer;
this.proxyPlayer = proxyPlayer; this.proxyPlayer = proxyPlayer;
this.server = server; this.server = server;
} }
@ -55,12 +54,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public CompletableFuture<ConnectionRequestBuilder.Result> connect() { public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>(); CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
server.initializeGenericBootstrap() server.initializeGenericBootstrap()
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ch.pipeline() ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
@ -73,7 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
ch.pipeline().addLast(HANDLER, connection); ch.pipeline().addLast(HANDLER, connection);
} }
}) })
.connect(serverInfo.getAddress()) .connect(registeredServer.getServerInfo().getAddress())
.addListener(new ChannelFutureListener() { .addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
@ -95,7 +93,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their // separated by \0 (the null byte). In order, you send the original host, the player's IP, their
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang). // UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
return serverInfo.getAddress().getHostString() + "\0" + return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
proxyPlayer.getRemoteAddress().getHostString() + "\0" + proxyPlayer.getRemoteAddress().getHostString() + "\0" +
proxyPlayer.getProfile().getId() + "\0" + proxyPlayer.getProfile().getId() + "\0" +
GSON.toJson(proxyPlayer.getProfile().getProperties()); GSON.toJson(proxyPlayer.getProfile().getProperties());
@ -113,9 +111,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
} else if (proxyPlayer.getConnection().isLegacyForge()) { } else if (proxyPlayer.getConnection().isLegacyForge()) {
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
} else { } else {
handshake.setServerAddress(serverInfo.getAddress().getHostString()); handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
} }
handshake.setPort(serverInfo.getAddress().getPort()); handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
minecraftConnection.write(handshake); minecraftConnection.write(handshake);
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
@ -127,12 +125,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
minecraftConnection.write(login); minecraftConnection.write(login);
} }
public void writeIfJoined(PluginMessage message) {
if (hasCompletedJoin) {
minecraftConnection.write(message);
}
}
public MinecraftConnection getMinecraftConnection() { public MinecraftConnection getMinecraftConnection() {
return minecraftConnection; return minecraftConnection;
} }
@Override
public VelocityRegisteredServer getServer() {
return registeredServer;
}
@Override
public ServerInfo getServerInfo() { public ServerInfo getServerInfo() {
return serverInfo; return registeredServer.getServerInfo();
} }
@Override @Override
@ -141,23 +151,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
} }
public void disconnect() { public void disconnect() {
if (minecraftConnection != null) {
minecraftConnection.close(); minecraftConnection.close();
minecraftConnection = null; minecraftConnection = null;
gracefulDisconnect = true;
}
} }
@Override @Override
public String toString() { public String toString() {
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
} }
@Override @Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data"); Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage(); PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId()); message.setChannel(identifier.getId());
message.setData(data); message.setData(data);
minecraftConnection.write(message); minecraftConnection.write(message);
return true;
} }
public boolean isLegacyForge() { public boolean isLegacyForge() {
@ -175,4 +189,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public void setHasCompletedJoin(boolean hasCompletedJoin) { public void setHasCompletedJoin(boolean hasCompletedJoin) {
this.hasCompletedJoin = hasCompletedJoin; this.hasCompletedJoin = hasCompletedJoin;
} }
public boolean isGracefulDisconnect() {
return gracefulDisconnect;
}
} }

Datei anzeigen

@ -1,14 +1,15 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.messages.MessageHandler; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.proxy.util.ThrowableUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -33,6 +34,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private boolean spawned = false; private boolean spawned = false;
private final List<UUID> serverBossBars = new ArrayList<>(); private final List<UUID> serverBossBars = new ArrayList<>();
private final Set<String> clientPluginMsgChannels = new HashSet<>(); private final Set<String> clientPluginMsgChannels = new HashSet<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server; private final VelocityServer server;
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
@ -53,6 +55,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void handle(MinecraftPacket packet) { public void handle(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (packet instanceof KeepAlive) { if (packet instanceof KeepAlive) {
KeepAlive keepAlive = (KeepAlive) packet; KeepAlive keepAlive = (KeepAlive) packet;
if (keepAlive.getRandomId() != lastPingID) { if (keepAlive.getRandomId() != lastPingID) {
@ -62,7 +70,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
player.setPing(System.currentTimeMillis() - lastPingSent); player.setPing(System.currentTimeMillis() - lastPingSent);
resetPingData(); resetPingData();
player.getConnectedServer().getMinecraftConnection().write(packet); serverConnection.getMinecraftConnection().write(packet);
return; return;
} }
@ -110,7 +118,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
player.getConnection().write(response); player.getConnection().write(response);
} else { } else {
player.getConnectedServer().getMinecraftConnection().write(packet); serverConnection.getMinecraftConnection().write(packet);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e); logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e);
@ -125,15 +133,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
// If we don't want to handle this packet, just forward it on. // If we don't want to handle this packet, just forward it on.
if (player.getConnectedServer().hasCompletedJoin()) { if (serverConnection.hasCompletedJoin()) {
player.getConnectedServer().getMinecraftConnection().write(packet); serverConnection.getMinecraftConnection().write(packet);
} }
} }
@Override @Override
public void handleUnknown(ByteBuf buf) { public void handleUnknown(ByteBuf buf) {
if (player.getConnectedServer().hasCompletedJoin()) { VelocityServerConnection serverConnection = player.getConnectedServer();
player.getConnectedServer().getMinecraftConnection().write(buf.retain()); if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (serverConnection.hasCompletedJoin()) {
serverConnection.getMinecraftConnection().write(buf.retain());
} }
} }
@ -162,11 +176,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
public void handleBackendJoinGame(JoinGame joinGame) { public void handleBackendJoinGame(JoinGame joinGame) {
resetPingData(); // reset ping data; resetPingData(); // reset ping data
if (!spawned) { if (!spawned) {
// nothing special to do here // Nothing special to do with regards to spawning the player
spawned = true; spawned = true;
player.getConnection().delayedWrite(joinGame); player.getConnection().delayedWrite(joinGame);
// We have something special to do for legacy Forge servers - during first connection the FML handshake
// will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on
// first switch.
//
// As we know that calling this branch only happens on first join, we set that if we are a Forge
// client that we must reset on the next switch.
//
// The call will handle if the player is not a Forge player appropriately.
player.getConnection().setCanSendLegacyFMLResetPacket(true);
} else { } else {
// Ah, this is the meat and potatoes of the whole venture! // Ah, this is the meat and potatoes of the whole venture!
// //
@ -210,6 +234,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
channel, toRegister)); channel, toRegister));
} }
// If we had plugin messages queued during login/FML handshake, send them now.
PluginMessage pm;
while ((pm = loginPluginMessages.poll()) != null) {
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
}
// Flush everything // Flush everything
player.getConnection().flush(); player.getConnection().flush();
player.getConnectedServer().getMinecraftConnection().flush(); player.getConnectedServer().getMinecraftConnection().flush();
@ -234,8 +264,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return serverBossBars; return serverBossBars;
} }
public void handleClientPluginMessage(PluginMessage packet) { private void handleClientPluginMessage(PluginMessage packet) {
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) { if (PluginMessageUtil.isMCRegister(packet)) {
List<String> actuallyRegistered = new ArrayList<>(); List<String> actuallyRegistered = new ArrayList<>();
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
for (String channel : channels) { for (String channel : channels) {
@ -252,31 +282,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered); PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket); player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
} }
} else if (PluginMessageUtil.isMCUnregister(packet)) {
return;
}
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
clientPluginMsgChannels.removeAll(channels); clientPluginMsgChannels.removeAll(channels);
} player.getConnectedServer().getMinecraftConnection().write(packet);
} else if (PluginMessageUtil.isMCBrand(packet)) {
if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet)); player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
return; } else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
} if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
// Always forward the FML handshake to the remote server.
if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
// Ensure that the messages are forwarded
player.getConnectedServer().getMinecraftConnection().write(packet); player.getConnectedServer().getMinecraftConnection().write(packet);
return; } else {
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
// be sent once the JoinGame packet has been received by the proxy.
loginPluginMessages.add(packet);
} }
} else {
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player, ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
ChannelSide.FROM_CLIENT, packet); if (id == null) {
if (status == MessageHandler.ForwardStatus.FORWARD) {
// We're going to forward on the original packet.
player.getConnectedServer().getMinecraftConnection().write(packet); player.getConnectedServer().getMinecraftConnection().write(packet);
} else {
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
player.getConnectedServer().getMinecraftConnection().write(packet);
}
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
}
} }
} }

Datei anzeigen

@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.player.SkinParts; import com.velocitypowered.api.proxy.player.SkinParts;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import java.util.Locale; import java.util.Locale;
public class ClientSettingsWrapper implements PlayerSettings { public class ClientSettingsWrapper implements PlayerSettings {

Datei anzeigen

@ -7,28 +7,24 @@ import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.PermissionProvider; import com.velocitypowered.api.permission.PermissionProvider;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
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.proxy.util.ThrowableUtils;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent; import net.kyori.text.TranslatableComponent;
@ -159,8 +155,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
@Override @Override
public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) { public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
return new ConnectionRequestBuilderImpl(info); return new ConnectionRequestBuilderImpl(server);
} }
@Override @Override
@ -195,52 +191,57 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return connectedServer; return connectedServer;
} }
public void handleConnectionException(ServerInfo info, Throwable throwable) { public void handleConnectionException(RegisteredServer server, Throwable throwable) {
String error = ThrowableUtils.briefDescription(throwable); String error = ThrowableUtils.briefDescription(throwable);
String userMessage; String userMessage;
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
userMessage = "Exception in server " + info.getName(); userMessage = "Exception in server " + server.getServerInfo().getName();
} else { } else {
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable); logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable);
userMessage = "Exception connecting to server " + info.getName(); userMessage = "Exception connecting to server " + server.getServerInfo().getName();
} }
handleConnectionException(info, null, TextComponent.builder() handleConnectionException(server, null, TextComponent.builder()
.content(userMessage + ": ") .content(userMessage + ": ")
.color(TextColor.RED) .color(TextColor.RED)
.append(TextComponent.of(error, TextColor.WHITE)) .append(TextComponent.of(error, TextColor.WHITE))
.build()); .build());
} }
public void handleConnectionException(ServerInfo info, Disconnect disconnect) { public void handleConnectionException(RegisteredServer server, Disconnect disconnect) {
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason()); Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason); logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason, TextComponent.builder()
.content("Kicked from " + server.getServerInfo().getName() + ": ")
.color(TextColor.RED)
.append(disconnectReason)
.build());
} else { } else {
logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason); logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason);
} handleConnectionException(server, disconnectReason, TextComponent.builder()
handleConnectionException(info, disconnectReason, TextComponent.builder() .content("Unable to connect to " + server.getServerInfo().getName() + ": ")
.content("Unable to connect to " + info.getName() + ": ")
.color(TextColor.RED) .color(TextColor.RED)
.append(disconnectReason) .append(disconnectReason)
.build()); .build());
} }
}
private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) { private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) {
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);; boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo());
connectionInFlight = null; connectionInFlight = null;
if (connectedServer == null) { if (connectedServer == null) {
// The player isn't yet connected to a server. // The player isn't yet connected to a server.
Optional<ServerInfo> nextServer = getNextServerToTry(); Optional<RegisteredServer> nextServer = getNextServerToTry();
if (nextServer.isPresent()) { if (nextServer.isPresent()) {
createConnectionRequest(nextServer.get()).fireAndForget(); createConnectionRequest(nextServer.get()).fireAndForget();
} else { } else {
connection.closeWith(Disconnect.create(friendlyReason)); connection.closeWith(Disconnect.create(friendlyReason));
} }
} else if (connectedServer.getServerInfo().equals(info)) { } else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) {
// Already connected to the server being disconnected from. // Already connected to the server being disconnected from.
if (kickReason != null) { if (kickReason != null) {
server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason)) server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason))
.thenAcceptAsync(event -> { .thenAcceptAsync(event -> {
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) { if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult(); KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult();
@ -261,7 +262,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
} }
Optional<ServerInfo> getNextServerToTry() { Optional<RegisteredServer> getNextServerToTry() {
List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder(); List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder();
if (tryIndex >= serversToTry.size()) { if (tryIndex >= serversToTry.size()) {
return Optional.empty(); return Optional.empty();
@ -272,21 +273,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return server.getServers().getServer(toTryName); return server.getServers().getServer(toTryName);
} }
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) { private Optional<ConnectionRequestBuilder.Status> checkServer(RegisteredServer server) {
Preconditions.checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server.");
if (connectionInFlight != null) { if (connectionInFlight != null) {
return CompletableFuture.completedFuture( return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS);
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS) }
); if (connectedServer != null && connectedServer.getServer().equals(server)) {
return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED);
}
return Optional.empty();
} }
if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) { private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
return CompletableFuture.completedFuture( Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(request.getServer());
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED) if (initialCheck.isPresent()) {
); return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get()));
} }
// Otherwise, initiate the connection. // Otherwise, initiate the connection.
ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer())); ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
return server.getEventManager().fire(event) return server.getEventManager().fire(event)
.thenCompose((newEvent) -> { .thenCompose((newEvent) -> {
if (!newEvent.getResult().isAllowed()) { if (!newEvent.getResult().isAllowed()) {
@ -295,7 +300,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
); );
} }
return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect(); RegisteredServer rs = newEvent.getResult().getServer().get();
Optional<ConnectionRequestBuilder.Status> lastCheck = checkServer(rs);
if (lastCheck.isPresent()) {
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get()));
}
return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect();
}); });
} }
@ -341,25 +351,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
@Override @Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data"); Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage(); PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId()); message.setChannel(identifier.getId());
message.setData(data); message.setData(data);
connection.write(message); connection.write(message);
return true;
} }
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final ServerInfo info; private final RegisteredServer server;
ConnectionRequestBuilderImpl(ServerInfo info) { ConnectionRequestBuilderImpl(RegisteredServer server) {
this.info = Preconditions.checkNotNull(info, "info"); this.server = Preconditions.checkNotNull(server, "info");
} }
@Override @Override
public ServerInfo getServer() { public RegisteredServer getServer() {
return info; return server;
} }
@Override @Override
@ -372,7 +383,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connect() connect()
.whenCompleteAsync((status, throwable) -> { .whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
handleConnectionException(info, throwable); handleConnectionException(server, throwable);
return; return;
} }
@ -387,7 +398,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// Ignored; the plugin probably already handled this. // Ignored; the plugin probably already handled this.
break; break;
case SERVER_DISCONNECTED: case SERVER_DISCONNECTED:
handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
break; break;
} }
}, connection.getChannel().eventLoop()); }, connection.getChannel().eventLoop());

Datei anzeigen

@ -5,12 +5,12 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent; import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
public class InitialConnectSessionHandler implements MinecraftSessionHandler { public class InitialConnectSessionHandler implements MinecraftSessionHandler {
private final ConnectedPlayer player; private final ConnectedPlayer player;

Datei anzeigen

@ -2,22 +2,23 @@ package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.EncryptionUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
@ -39,6 +40,7 @@ import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final String MOJANG_SERVER_AUTH_URL = private static final String MOJANG_SERVER_AUTH_URL =
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
@ -157,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
return; return;
} }
if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) { if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
// Request encryption. // Request encryption.
EncryptionRequest request = generateRequest(); EncryptionRequest request = generateRequest();
this.verify = Arrays.copyOf(request.getVerifyToken(), 4); this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
@ -218,7 +220,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
} }
private void handleProxyLogin(ConnectedPlayer player) { private void handleProxyLogin(ConnectedPlayer player) {
Optional<ServerInfo> toTry = player.getNextServerToTry(); Optional<RegisteredServer> toTry = player.getNextServerToTry();
if (!toTry.isPresent()) { if (!toTry.isPresent()) {
player.close(TextComponent.of("No available servers", TextColor.RED)); player.close(TextComponent.of("No available servers", TextColor.RED));
return; return;
@ -244,7 +246,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
logger.info("{} has connected", player); logger.info("{} has connected", player);
inbound.setSessionHandler(new InitialConnectSessionHandler(player)); inbound.setSessionHandler(new InitialConnectSessionHandler(player));
player.createConnectionRequest(toTry.get()).fireAndForget(); server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
} }
@Override @Override

Datei anzeigen

@ -4,16 +4,16 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.StatusPing; import com.velocitypowered.proxy.protocol.packet.StatusPing;
import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse; import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;

Datei anzeigen

@ -7,6 +7,7 @@ public class ConnectionMessages {
public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED); public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED);
public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED); public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED);
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error"); public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error");
public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?");
private ConnectionMessages() { private ConnectionMessages() {
throw new AssertionError(); throw new AssertionError();

Datei anzeigen

@ -4,7 +4,9 @@ import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import net.minecrell.terminalconsole.SimpleTerminalConsole; import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.jline.reader.*; import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;

Datei anzeigen

@ -2,10 +2,10 @@ package com.velocitypowered.proxy.messages;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.messages.*; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import org.apache.logging.log4j.LogManager; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import org.apache.logging.log4j.Logger; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class VelocityChannelRegistrar implements ChannelRegistrar { public class VelocityChannelRegistrar implements ChannelRegistrar {
private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class);
private final Map<String, MessageHandler> handlers = new ConcurrentHashMap<>();
private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>(); private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>();
@Override @Override
public void register(MessageHandler handler, ChannelIdentifier... identifiers) { public void register(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier, Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier,
"identifier is unknown"); "identifier is unknown");
} }
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
handlers.put(identifier.getId(), handler);
identifierMap.put(identifier.getId(), identifier); identifierMap.put(identifier.getId(), identifier);
} }
} }
public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) {
MessageHandler handler = handlers.get(message.getChannel());
ChannelIdentifier identifier = identifierMap.get(message.getChannel());
if (handler == null || identifier == null) {
return MessageHandler.ForwardStatus.FORWARD;
}
try {
return handler.handle(source, side, identifier, message.getData());
} catch (Exception e) {
logger.info("Unable to handle plugin message on channel {} for {}", message.getChannel(), source);
// In case of doubt, do not forward the message on.
return MessageHandler.ForwardStatus.HANDLED;
}
}
@Override @Override
public void unregister(ChannelIdentifier... identifiers) { public void unregister(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
} }
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
handlers.remove(identifier.getId());
identifierMap.remove(identifier.getId()); identifierMap.remove(identifier.getId());
} }
} }
@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
public boolean registered(String id) { public boolean registered(String id) {
return identifierMap.containsKey(id); return identifierMap.containsKey(id);
} }
public ChannelIdentifier getFromId(String id) {
return identifierMap.get(id);
}
} }

Datei anzeigen

@ -7,21 +7,11 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler; import com.velocitypowered.proxy.protocol.netty.*;
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 io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*; import io.netty.channel.*;
import io.netty.channel.epoll.Epoll; import io.netty.channel.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.kqueue.*; import io.netty.channel.kqueue.*;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
@ -75,7 +65,7 @@ public final class ConnectionManager {
@Override @Override
protected void initChannel(final Channel ch) { protected void initChannel(final Channel ch) {
ch.pipeline() ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder()) .addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE) .addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
@ -125,7 +115,9 @@ public final class ConnectionManager {
public Bootstrap createWorker() { public Bootstrap createWorker() {
return new Bootstrap() return new Bootstrap()
.channel(this.transportType.socketChannelClass) .channel(this.transportType.socketChannelClass)
.group(this.workerGroup); .group(this.workerGroup)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout());
} }
public void shutdown() { public void shutdown() {

Datei anzeigen

@ -13,7 +13,4 @@ public interface Connections {
String MINECRAFT_DECODER = "minecraft-decoder"; String MINECRAFT_DECODER = "minecraft-decoder";
String MINECRAFT_ENCODER = "minecraft-encoder"; String MINECRAFT_ENCODER = "minecraft-encoder";
String READ_TIMEOUT = "read-timeout"; String READ_TIMEOUT = "read-timeout";
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
} }

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.proxy.network.http;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*; import io.netty.channel.Channel;
import io.netty.channel.pool.*; import io.netty.channel.pool.*;
import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;

Datei anzeigen

@ -13,7 +13,10 @@ import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory;
import net.kyori.event.EventSubscriber; import net.kyori.event.EventSubscriber;
import net.kyori.event.PostResult; import net.kyori.event.PostResult;
import net.kyori.event.SimpleEventBus; import net.kyori.event.SimpleEventBus;
import net.kyori.event.method.*; import net.kyori.event.method.EventExecutor;
import net.kyori.event.method.MethodScanner;
import net.kyori.event.method.MethodSubscriptionAdapter;
import net.kyori.event.method.SimpleMethodSubscriptionAdapter;
import net.kyori.event.method.asm.ASMEventExecutorFactory; import net.kyori.event.method.asm.ASMEventExecutorFactory;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.ArrayList;
import java.util.concurrent.*; import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class VelocityEventManager implements EventManager { public class VelocityEventManager implements EventManager {
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.plugin; package com.velocitypowered.proxy.plugin;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -2,7 +2,9 @@ package com.velocitypowered.proxy.plugin.loader;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.velocitypowered.api.plugin.*; import com.velocitypowered.api.plugin.InvalidPluginException;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -8,7 +8,6 @@ import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

Datei anzeigen

@ -8,7 +8,7 @@ import io.netty.util.collection.IntObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*; import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
@ -23,7 +23,7 @@ public enum StateRegistry {
}, },
STATUS { STATUS {
{ {
SERVERBOUND.register(StatusRequest.class, StatusRequest::new, SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
genericMappings(0x00)); genericMappings(0x00));
SERVERBOUND.register(StatusPing.class, StatusPing::new, SERVERBOUND.register(StatusPing.class, StatusPing::new,
genericMappings(0x01)); genericMappings(0x01));

Datei anzeigen

@ -101,9 +101,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
queryResponse.writeByte(QUERY_TYPE_STAT); queryResponse.writeByte(QUERY_TYPE_STAT);
queryResponse.writeInt(sessionId); queryResponse.writeInt(sessionId);
// Fetch information
Collection<Player> players = server.getAllPlayers();
// Start writing the response // Start writing the response
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0); ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent())); responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
@ -114,12 +111,14 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
responseWriter.write("plugins", ""); responseWriter.write("plugins", "");
responseWriter.write("map", "Velocity"); responseWriter.write("map", "Velocity");
responseWriter.write("numplayers", players.size()); responseWriter.write("numplayers", server.getPlayerCount());
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers()); responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
responseWriter.write("hostport", server.getConfiguration().getBind().getPort()); responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString()); responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
responseWriter.writePlayers(players); if (!responseWriter.isBasic) {
responseWriter.writePlayers(server.getAllPlayers());
}
break; break;
} }
@ -132,6 +131,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
ctx.writeAndFlush(responsePacket); ctx.writeAndFlush(responsePacket);
} catch (Exception e) { } catch (Exception e) {
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e); logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
// count.
responsePacket.release(); responsePacket.release();
} }
} }

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;

Datei anzeigen

@ -1,7 +1,10 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.*; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.CorruptedFrameException;

Datei anzeigen

@ -4,7 +4,6 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List; import java.util.List;

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.text.Component; import net.kyori.text.Component;

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.text.Component; import net.kyori.text.Component;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction; import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer; import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.ComponentSerializers;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
public class HeaderAndFooter implements MinecraftPacket { public class HeaderAndFooter implements MinecraftPacket {
private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}"); private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}");

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,10 +1,16 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
public class StatusRequest implements MinecraftPacket { public class StatusRequest implements MinecraftPacket {
public static final StatusRequest INSTANCE = new StatusRequest();
private StatusRequest() {
}
@Override @Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
@ -14,4 +20,9 @@ public class StatusRequest implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
} }
@Override
public String toString() {
return "StatusRequest";
}
} }

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -20,13 +19,20 @@ public enum PluginMessageUtil {
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand"); return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand");
} }
public static boolean isMCRegister(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register");
}
public static boolean isMCUnregister(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister");
}
public static List<String> getChannels(PluginMessage message) { public static List<String> getChannels(PluginMessage message) {
Preconditions.checkNotNull(message, "message"); Preconditions.checkNotNull(message, "message");
Preconditions.checkArgument(message.getChannel().equals("REGISTER") || Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s",
message.getChannel().equals("UNREGISTER") || message.getChannel());
message.getChannel().equals("minecraft:register") ||
message.getChannel().equals("minecraft:unregister"),
"Unknown channel type " + message.getChannel());
String channels = new String(message.getData(), StandardCharsets.UTF_8); String channels = new String(message.getData(), StandardCharsets.UTF_8);
return ImmutableList.copyOf(channels.split("\0")); return ImmutableList.copyOf(channels.split("\0"));
} }

Datei anzeigen

@ -16,7 +16,6 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class VelocityScheduler implements Scheduler { public class VelocityScheduler implements Scheduler {
private final PluginManager pluginManager; private final PluginManager pluginManager;
@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler {
} }
@Override @Override
public TaskBuilder delay(int time, TimeUnit unit) { public TaskBuilder delay(long time, TimeUnit unit) {
this.delay = unit.toMillis(time); this.delay = unit.toMillis(time);
return this; return this;
} }
@Override @Override
public TaskBuilder repeat(int time, TimeUnit unit) { public TaskBuilder repeat(long time, TimeUnit unit) {
this.repeat = unit.toMillis(time); this.repeat = unit.toMillis(time);
return this; return this;
} }
@ -91,69 +90,17 @@ public class VelocityScheduler implements Scheduler {
@Override @Override
public ScheduledTask schedule() { public ScheduledTask schedule() {
if (delay == 0 && repeat == 0) {
// A special purpose, simplified implementation
VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable);
tasksByPlugin.put(plugin, task);
taskService.execute(task);
return task;
} else {
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat); VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
tasksByPlugin.put(plugin, task); tasksByPlugin.put(plugin, task);
return task; return task;
} }
} }
}
private class VelocityImmediatelyScheduledTask implements ScheduledTask, Runnable {
private final Object plugin;
private final Runnable runnable;
private final AtomicReference<TaskStatus> status;
private Thread taskThread;
private VelocityImmediatelyScheduledTask(Object plugin, Runnable runnable) {
this.plugin = plugin;
this.runnable = runnable;
this.status = new AtomicReference<>(TaskStatus.SCHEDULED);
}
@Override
public Object plugin() {
return plugin;
}
@Override
public TaskStatus status() {
return status.get();
}
@Override
public void cancel() {
if (status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.CANCELLED)) {
if (taskThread != null) {
taskThread.interrupt();
}
}
}
@Override
public void run() {
taskThread = Thread.currentThread();
try {
runnable.run();
} catch (Exception e) {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
}
status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.FINISHED);
taskThread = null;
tasksByPlugin.remove(plugin, this);
}
}
private class VelocityTask implements Runnable, ScheduledTask { private class VelocityTask implements Runnable, ScheduledTask {
private final Object plugin; private final Object plugin;
private final Runnable runnable; private final Runnable runnable;
private ScheduledFuture<?> future; private ScheduledFuture<?> future;
private volatile Thread currentTaskThread;
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) { private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
this.plugin = plugin; this.plugin = plugin;
@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler {
public void cancel() { public void cancel() {
if (future != null) { if (future != null) {
future.cancel(false); future.cancel(false);
Thread cur = currentTaskThread;
if (cur != null) {
cur.interrupt();
}
onFinish(); onFinish();
} }
} }
@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler {
@Override @Override
public void run() { public void run() {
taskService.execute(() -> { taskService.execute(() -> {
currentTaskThread = Thread.currentThread();
try { try {
runnable.run(); runnable.run();
} catch (Exception e) { } catch (Exception e) {
@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
} }
} }
currentTaskThread = null;
}); });
} }

Datei anzeigen

@ -0,0 +1,49 @@
package com.velocitypowered.proxy.server;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ServerMap {
private final VelocityServer server;
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
public ServerMap(VelocityServer server) {
this.server = server;
}
public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "server");
String lowerName = name.toLowerCase(Locale.US);
return Optional.ofNullable(servers.get(lowerName));
}
public Collection<RegisteredServer> getAllServers() {
return ImmutableList.copyOf(servers.values());
}
public RegisteredServer register(ServerInfo serverInfo) {
Preconditions.checkNotNull(serverInfo, "serverInfo");
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo);
Preconditions.checkArgument(servers.putIfAbsent(lowerName, rs) == null, "Server with name %s already registered", serverInfo.getName());
return rs;
}
public void unregister(ServerInfo serverInfo) {
Preconditions.checkNotNull(serverInfo, "serverInfo");
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
RegisteredServer rs = servers.get(lowerName);
Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName());
Preconditions.checkArgument(rs.getServerInfo().equals(serverInfo), "Trying to remove server %s with differing information", serverInfo.getName());
Preconditions.checkState(servers.remove(lowerName, rs), "Server with name %s replaced whilst unregistering", serverInfo.getName());
}
}

Datei anzeigen

@ -0,0 +1,112 @@
package com.velocitypowered.proxy.server;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.server.ping.PingSessionHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static com.velocitypowered.proxy.network.Connections.*;
public class VelocityRegisteredServer implements RegisteredServer {
private final VelocityServer server;
private final ServerInfo serverInfo;
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) {
this.server = server;
this.serverInfo = serverInfo;
}
@Override
public ServerInfo getServerInfo() {
return serverInfo;
}
@Override
public Collection<Player> getPlayersConnected() {
return ImmutableList.copyOf(players);
}
@Override
public CompletableFuture<ServerPing> ping() {
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.initializeGenericBootstrap()
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND));
MinecraftConnection connection = new MinecraftConnection(ch, server);
connection.setState(StateRegistry.HANDSHAKE);
ch.pipeline().addLast(HANDLER, connection);
}
})
.connect(serverInfo.getAddress())
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler(new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn));
} else {
pingFuture.completeExceptionally(future.cause());
}
}
});
return pingFuture;
}
public void addPlayer(ConnectedPlayer player) {
players.add(player);
}
public void removePlayer(ConnectedPlayer player) {
players.remove(player);
}
@Override
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
for (ConnectedPlayer player : players) {
if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) {
ServerConnection connection = player.getConnectedServer();
return connection.sendPluginMessage(identifier, data);
}
}
return false;
}
@Override
public String toString() {
return "registered server: " + serverInfo;
}
}

Datei anzeigen

@ -0,0 +1,68 @@
package com.velocitypowered.proxy.server.ping;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
public class PingSessionHandler implements MinecraftSessionHandler {
private final CompletableFuture<ServerPing> result;
private final RegisteredServer server;
private final MinecraftConnection connection;
private boolean completed = false;
public PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server, MinecraftConnection connection) {
this.result = result;
this.server = server;
this.connection = connection;
}
@Override
public void activated() {
Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.STATUS_ID);
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
connection.write(handshake);
connection.setState(StateRegistry.STATUS);
connection.write(StatusRequest.INSTANCE);
}
@Override
public void handle(MinecraftPacket packet) {
Preconditions.checkState(packet instanceof StatusResponse, "Did not get status response back from connection");
// All good!
completed = true;
connection.close();
ServerPing ping = VelocityServer.GSON.fromJson(((StatusResponse) packet).getStatus(), ServerPing.class);
result.complete(ping);
}
@Override
public void disconnected() {
if (!completed) {
result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
}
}
@Override
public void exception(Throwable throwable) {
completed = true;
result.completeExceptionally(throwable);
}
}

Datei anzeigen

@ -1,56 +0,0 @@
package com.velocitypowered.proxy.util;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ServerMap {
private final Map<String, ServerInfo> servers = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Optional<ServerInfo> getServer(String server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.toLowerCase(Locale.US);
lock.readLock().lock();
try {
return Optional.ofNullable(servers.get(lowerName));
} finally {
lock.readLock().unlock();
}
}
public Collection<ServerInfo> getAllServers() {
lock.readLock().lock();
try {
return ImmutableList.copyOf(servers.values());
} finally {
lock.readLock().unlock();
}
}
public void register(ServerInfo server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.getName().toLowerCase(Locale.US);
lock.writeLock().lock();
try {
Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName());
} finally {
lock.writeLock().unlock();
}
}
public void unregister(ServerInfo server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.getName().toLowerCase(Locale.US);
lock.writeLock().lock();
try {
Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!");
} finally {
lock.writeLock().unlock();
}
}
}

Datei anzeigen

@ -6,7 +6,6 @@ import com.google.common.collect.MapMaker;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
/** /**

Datei anzeigen

@ -3,9 +3,7 @@ package com.velocitypowered.proxy.protocol;
import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.Handshake;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12; import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class PacketRegistryTest { class PacketRegistryTest {

Datei anzeigen

@ -9,7 +9,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
class VelocitySchedulerTest { class VelocitySchedulerTest {
// TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep() // TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep()

Datei anzeigen

@ -7,7 +7,8 @@ import java.net.InetAddress;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RatelimiterTest { class RatelimiterTest {

Datei anzeigen

@ -1,31 +1,34 @@
package com.velocitypowered.proxy.util; package com.velocitypowered.proxy.util;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.server.ServerMap;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Optional; import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ServerMapTest { class ServerMapTest {
private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565); private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565);
@Test @Test
void respectsCaseInsensitivity() { void respectsCaseInsensitivity() {
ServerMap map = new ServerMap(); ServerMap map = new ServerMap(null);
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
map.register(info); RegisteredServer connection = map.register(info);
assertEquals(Optional.of(info), map.getServer("TestServer")); assertEquals(Optional.of(connection), map.getServer("TestServer"));
assertEquals(Optional.of(info), map.getServer("testserver")); assertEquals(Optional.of(connection), map.getServer("testserver"));
assertEquals(Optional.of(info), map.getServer("TESTSERVER")); assertEquals(Optional.of(connection), map.getServer("TESTSERVER"));
} }
@Test @Test
void rejectsRepeatedRegisterAttempts() { void rejectsRepeatedRegisterAttempts() {
ServerMap map = new ServerMap(); ServerMap map = new ServerMap(null);
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
map.register(info); map.register(info);