Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-12-25 15:50:19 +01:00
merge upstream
Dieser Commit ist enthalten in:
Commit
29b82ae301
1
.gitignore
vendored
1
.gitignore
vendored
@ -135,3 +135,4 @@ logs/
|
||||
server-icon.png
|
||||
/bin/
|
||||
run/
|
||||
plugins/
|
@ -19,7 +19,8 @@ import javax.tools.StandardLocation;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
|
||||
public class PluginAnnotationProcessor extends AbstractProcessor {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
+ '}';
|
||||
}
|
||||
}
|
@ -3,12 +3,13 @@ package com.velocitypowered.api.event.connection;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This event is fired when a player has initiated a connection with the proxy but before the proxy authenticates the
|
||||
* player with Mojang or before the player's proxy connection is fully established (for offline mode).
|
||||
@ -52,44 +53,59 @@ public class PreLoginEvent implements ResultedEvent<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 {
|
||||
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
|
||||
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
|
||||
public static class PreLoginComponentResult implements ResultedEvent.Result {
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode.
|
||||
* @param allowedOnlineMode if true, online mode will be used for the connection
|
||||
*/
|
||||
private PreLoginComponentResult(boolean allowedOnlineMode) {
|
||||
super(true, null);
|
||||
this.onlineMode = allowedOnlineMode;
|
||||
private final Result result;
|
||||
private final Optional<Component> reason;
|
||||
|
||||
private PreLoginComponentResult(Result result, @Nullable Component reason) {
|
||||
this.result = result;
|
||||
this.reason = Optional.ofNullable(reason);
|
||||
}
|
||||
|
||||
private PreLoginComponentResult(@Nullable Component reason) {
|
||||
super(reason == null, reason);
|
||||
// Don't care about this
|
||||
this.onlineMode = false;
|
||||
@Override
|
||||
public boolean isAllowed() {
|
||||
return result != Result.DISALLOWED;
|
||||
}
|
||||
|
||||
public Optional<Component> getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public boolean isOnlineModeAllowed() {
|
||||
return this.onlineMode;
|
||||
return result == Result.FORCE_ONLINE;
|
||||
}
|
||||
|
||||
public boolean isForceOfflineMode() {
|
||||
return result == Result.FORCE_OFFLINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isForceOfflineMode()) {
|
||||
return "allowed with force offline mode";
|
||||
}
|
||||
if (isOnlineModeAllowed()) {
|
||||
return "allowed with online mode";
|
||||
}
|
||||
|
||||
return super.toString();
|
||||
if (isAllowed()) {
|
||||
return "allowed";
|
||||
}
|
||||
if (reason.isPresent()) {
|
||||
return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get());
|
||||
}
|
||||
return "denied";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a result indicating the connection will be allowed through the proxy.
|
||||
* Returns a result indicating the connection will be allowed through
|
||||
* the proxy.
|
||||
* @return the allowed result
|
||||
*/
|
||||
public static PreLoginComponentResult allowed() {
|
||||
@ -97,23 +113,41 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a result indicating the connection will be allowed through the proxy, but the connection will be
|
||||
* forced to use online mode provided that the proxy is in offline mode. This acts similarly to {@link #allowed()}
|
||||
* on an online-mode proxy.
|
||||
* Returns a result indicating the connection will be allowed through
|
||||
* the proxy, but the connection will be forced to use online mode
|
||||
* provided that the proxy is in offline mode. This acts similarly to
|
||||
* {@link #allowed()} on an online-mode proxy.
|
||||
* @return the result
|
||||
*/
|
||||
public static PreLoginComponentResult forceOnlineMode() {
|
||||
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.
|
||||
* @param reason the reason for disallowing the connection
|
||||
* @return a new result
|
||||
*/
|
||||
public static PreLoginComponentResult denied(@NonNull Component reason) {
|
||||
public static PreLoginComponentResult denied(Component reason) {
|
||||
Preconditions.checkNotNull(reason, "reason");
|
||||
return new PreLoginComponentResult(reason);
|
||||
return new PreLoginComponentResult(Result.DISALLOWED, reason);
|
||||
}
|
||||
|
||||
private enum Result {
|
||||
ALLOWED,
|
||||
FORCE_ONLINE,
|
||||
FORCE_OFFLINE,
|
||||
DISALLOWED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
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.velocitypowered.api.proxy.InboundConnection;
|
||||
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
|
||||
|
@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.kyori.text.Component;
|
||||
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> {
|
||||
private final Player player;
|
||||
private final ServerInfo server;
|
||||
private final RegisteredServer server;
|
||||
private final Component originalReason;
|
||||
private final boolean duringLogin;
|
||||
private ServerKickResult result;
|
||||
|
||||
public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) {
|
||||
public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
|
||||
@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
|
||||
return player;
|
||||
}
|
||||
|
||||
public ServerInfo getServer() {
|
||||
public RegisteredServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@ -91,9 +91,9 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
|
||||
* when this result is used.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
|
||||
return false;
|
||||
}
|
||||
|
||||
public ServerInfo getServer() {
|
||||
public RegisteredServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
|
||||
* @param server the server to send the player to
|
||||
* @return the redirect result
|
||||
*/
|
||||
public static RedirectPlayer create(ServerInfo server) {
|
||||
public static RedirectPlayer create(RegisteredServer server) {
|
||||
return new RedirectPlayer(server);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.api.event.player;
|
||||
|
||||
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.PlayerSettings;
|
||||
|
||||
public class PlayerSettingsChangedEvent {
|
||||
private final Player player;
|
||||
|
@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.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
|
||||
@ -10,9 +10,9 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
*/
|
||||
public class ServerConnectedEvent {
|
||||
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.server = Preconditions.checkNotNull(server, "server");
|
||||
}
|
||||
@ -21,7 +21,7 @@ public class ServerConnectedEvent {
|
||||
return player;
|
||||
}
|
||||
|
||||
public ServerInfo getServer() {
|
||||
public RegisteredServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@ -14,11 +14,13 @@ import java.util.Optional;
|
||||
*/
|
||||
public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> {
|
||||
private final Player player;
|
||||
private final RegisteredServer originalServer;
|
||||
private ServerResult result;
|
||||
|
||||
public ServerPreConnectEvent(Player player, ServerResult result) {
|
||||
public ServerPreConnectEvent(Player player, RegisteredServer originalServer) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.result = Preconditions.checkNotNull(result, "result");
|
||||
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
|
||||
this.result = ServerResult.allowed(originalServer);
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
this.result = Preconditions.checkNotNull(result, "result");
|
||||
}
|
||||
|
||||
public RegisteredServer getOriginalServer() {
|
||||
return originalServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerPreConnectEvent{" +
|
||||
"player=" + player +
|
||||
", originalServer=" + originalServer +
|
||||
", result=" + result +
|
||||
'}';
|
||||
}
|
||||
@ -50,11 +57,11 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
private static final ServerResult DENIED = new ServerResult(false, null);
|
||||
|
||||
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.info = info;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -62,8 +69,8 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public Optional<ServerInfo> getInfo() {
|
||||
return Optional.ofNullable(info);
|
||||
public Optional<RegisteredServer> getServer() {
|
||||
return Optional.ofNullable(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -71,14 +78,14 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
if (!allowed) {
|
||||
return "denied";
|
||||
}
|
||||
return "allowed: connect to " + info.getName();
|
||||
return "allowed: connect to " + server.getServerInfo().getName();
|
||||
}
|
||||
|
||||
public static ServerResult denied() {
|
||||
return DENIED;
|
||||
}
|
||||
|
||||
public static ServerResult allowed(ServerInfo server) {
|
||||
public static ServerResult allowed(RegisteredServer server) {
|
||||
Preconditions.checkNotNull(server, "server");
|
||||
return new ServerResult(true, server);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.velocitypowered.api.plugin.meta;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -1,29 +1,28 @@
|
||||
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 org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* Returns the server that this connection request represents.
|
||||
* @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
|
||||
* 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
|
||||
*/
|
||||
@NonNull CompletableFuture<Result> connect();
|
||||
CompletableFuture<Result> connect();
|
||||
|
||||
/**
|
||||
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error
|
||||
|
@ -1,11 +1,11 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import java.util.List;
|
||||
import net.kyori.text.Component;
|
||||
@ -65,10 +65,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
||||
|
||||
/**
|
||||
* Creates a new connection request so that the player can connect to another server.
|
||||
* @param info the server to connect to
|
||||
* @param server the server to connect to
|
||||
* @return a new connection request
|
||||
*/
|
||||
ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info);
|
||||
ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server);
|
||||
|
||||
/**
|
||||
* Gets a game profile properties of player
|
||||
|
@ -1,12 +1,13 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.CommandManager;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.EventManager;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
|
||||
import com.velocitypowered.api.scheduler.Scheduler;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.scheduler.Scheduler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collection;
|
||||
@ -45,23 +46,24 @@ public interface ProxyServer {
|
||||
int getPlayerCount();
|
||||
|
||||
/**
|
||||
* Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive.
|
||||
* Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive.
|
||||
* @param name the name of the server
|
||||
* @return the registered server, which may be empty
|
||||
*/
|
||||
Optional<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
|
||||
*/
|
||||
Collection<ServerInfo> getAllServers();
|
||||
Collection<RegisteredServer> getAllServers();
|
||||
|
||||
/**
|
||||
* Registers a server with this proxy. A server with this name should not already exist.
|
||||
* @param server the server to register
|
||||
* @return the newly registered server
|
||||
*/
|
||||
void registerServer(ServerInfo server);
|
||||
RegisteredServer registerServer(ServerInfo server);
|
||||
|
||||
/**
|
||||
* Unregisters this server from the proxy.
|
||||
|
@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
|
||||
/**
|
||||
@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi
|
||||
* Returns the server that this connection is connected to.
|
||||
* @return the server this connection is connected to
|
||||
*/
|
||||
RegisteredServer getServer();
|
||||
|
||||
/**
|
||||
* Returns the server info for this connection.
|
||||
* @return the server info for this connection
|
||||
*/
|
||||
ServerInfo getServerInfo();
|
||||
|
||||
/**
|
||||
|
@ -8,6 +8,7 @@ public interface ChannelMessageSink {
|
||||
* Sends a plugin message to this target.
|
||||
* @param identifier the channel identifier to send the message on
|
||||
* @param data the data to send
|
||||
* @return whether or not the message could be sent
|
||||
*/
|
||||
void sendPluginMessage(ChannelIdentifier identifier, byte[] data);
|
||||
boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data);
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
package com.velocitypowered.api.proxy.messages;
|
||||
|
||||
/**
|
||||
* Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from
|
||||
* the client or the server.
|
||||
* Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on.
|
||||
*/
|
||||
public interface ChannelRegistrar {
|
||||
/**
|
||||
* Registers the specified message handler to listen for plugin messages on the specified channels.
|
||||
* @param handler the handler to register
|
||||
* Registers the specified message identifiers to listen on for the
|
||||
* @param identifiers the channel identifiers to register
|
||||
*/
|
||||
void register(MessageHandler handler, ChannelIdentifier... identifiers);
|
||||
void register(ChannelIdentifier... identifiers);
|
||||
|
||||
/**
|
||||
* Unregisters the handler for the specified channel.
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import java.util.regex.Pattern;
|
||||
* Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use.
|
||||
*/
|
||||
public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
|
||||
|
||||
private final String namespace;
|
||||
private final String name;
|
||||
|
@ -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();
|
||||
}
|
@ -74,6 +74,7 @@ public class ServerPing {
|
||||
builder.favicon = favicon;
|
||||
builder.nullOutModinfo = modinfo == null;
|
||||
if (modinfo != null) {
|
||||
builder.modType = modinfo.type;
|
||||
builder.mods.addAll(modinfo.modList);
|
||||
}
|
||||
return builder;
|
||||
@ -91,6 +92,7 @@ public class ServerPing {
|
||||
private int onlinePlayers;
|
||||
private int maximumPlayers;
|
||||
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
|
||||
private String modType;
|
||||
private final List<Mod> mods = new ArrayList<>();
|
||||
private Component description;
|
||||
private Favicon favicon;
|
||||
@ -121,6 +123,11 @@ public class ServerPing {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder modType(String modType) {
|
||||
this.modType = Preconditions.checkNotNull(modType, "modType");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder mods(Mod... mods) {
|
||||
this.mods.addAll(Arrays.asList(mods));
|
||||
return this;
|
||||
@ -158,7 +165,7 @@ public class ServerPing {
|
||||
|
||||
public ServerPing build() {
|
||||
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon,
|
||||
nullOutModinfo ? null : new Modinfo(mods));
|
||||
nullOutModinfo ? null : new Modinfo(modType, mods));
|
||||
}
|
||||
|
||||
public Version getVersion() {
|
||||
@ -185,6 +192,10 @@ public class ServerPing {
|
||||
return favicon;
|
||||
}
|
||||
|
||||
public String getModType() {
|
||||
return modType;
|
||||
}
|
||||
|
||||
public List<Mod> getMods() {
|
||||
return mods;
|
||||
}
|
||||
@ -196,6 +207,7 @@ public class ServerPing {
|
||||
", onlinePlayers=" + onlinePlayers +
|
||||
", maximumPlayers=" + maximumPlayers +
|
||||
", samplePlayers=" + samplePlayers +
|
||||
", modType=" + modType +
|
||||
", mods=" + mods +
|
||||
", description=" + description +
|
||||
", favicon=" + favicon +
|
||||
@ -291,14 +303,23 @@ public class ServerPing {
|
||||
}
|
||||
|
||||
public static class Modinfo {
|
||||
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of());
|
||||
public static final Modinfo DEFAULT = new Modinfo("FML", ImmutableList.of());
|
||||
|
||||
private final String type = "FML";
|
||||
private final String type;
|
||||
private final List<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);
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public List<Mod> getMods() {
|
||||
return modList;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mod {
|
||||
|
@ -24,7 +24,7 @@ public interface Scheduler {
|
||||
* @param unit the unit of time for {@code time}
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
TaskBuilder delay(int time, TimeUnit unit);
|
||||
TaskBuilder delay(long time, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Specifies that the task should continue running after waiting for the specified amount, until it is cancelled.
|
||||
@ -32,7 +32,7 @@ public interface Scheduler {
|
||||
* @param unit the unit of time for {@code time}
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
TaskBuilder repeat(int time, TimeUnit unit);
|
||||
TaskBuilder repeat(long time, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Clears the delay on this task.
|
||||
|
@ -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("");
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ import java.util.function.Supplier;
|
||||
|
||||
public class NativeCodeLoader<T> implements Supplier<T> {
|
||||
private final List<Variant<T>> variants;
|
||||
private Variant<T> selected;
|
||||
private volatile Variant<T> selected;
|
||||
|
||||
public NativeCodeLoader(List<Variant<T>> variants) {
|
||||
this.variants = ImmutableList.copyOf(variants);
|
||||
@ -16,36 +16,41 @@ public class NativeCodeLoader<T> implements Supplier<T> {
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if (selected == null) {
|
||||
selected = select();
|
||||
}
|
||||
return selected.object;
|
||||
return tryLoad().object;
|
||||
}
|
||||
|
||||
private Variant<T> select() {
|
||||
for (Variant<T> variant : variants) {
|
||||
T got = variant.get();
|
||||
if (got == null) {
|
||||
continue;
|
||||
}
|
||||
return variant;
|
||||
private Variant<T> tryLoad() {
|
||||
if (selected != null) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (selected != null) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
for (Variant<T> variant : variants) {
|
||||
T got = variant.get();
|
||||
if (got == null) {
|
||||
continue;
|
||||
}
|
||||
selected = variant;
|
||||
return selected;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't find any suitable variants");
|
||||
}
|
||||
throw new IllegalArgumentException("Can't find any suitable variants");
|
||||
}
|
||||
|
||||
public String getLoadedVariant() {
|
||||
if (selected == null) {
|
||||
selected = select();
|
||||
}
|
||||
return selected.name;
|
||||
return tryLoad().name;
|
||||
}
|
||||
|
||||
static class Variant<T> {
|
||||
private boolean available;
|
||||
private volatile boolean available;
|
||||
private final Runnable setup;
|
||||
private final String name;
|
||||
private final T object;
|
||||
private boolean hasBeenSetup = false;
|
||||
private volatile boolean hasBeenSetup = false;
|
||||
|
||||
Variant(BooleanSupplier available, Runnable setup, String name, T object) {
|
||||
this.available = available.getAsBoolean();
|
||||
@ -54,27 +59,34 @@ public class NativeCodeLoader<T> implements Supplier<T> {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
if (available && !hasBeenSetup) {
|
||||
try {
|
||||
setup.run();
|
||||
hasBeenSetup = true;
|
||||
} catch (Exception e) {
|
||||
available = false;
|
||||
public T get() {
|
||||
if (!available) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make sure setup happens only once
|
||||
if (!hasBeenSetup) {
|
||||
synchronized (this) {
|
||||
// We change availability if need be below, may as well check it again here for sanity.
|
||||
if (!available) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Okay, now try the setup if we haven't done so yet.
|
||||
if (!hasBeenSetup) {
|
||||
try {
|
||||
setup.run();
|
||||
hasBeenSetup = true;
|
||||
return object;
|
||||
} catch (Exception e) {
|
||||
available = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (!hasBeenSetup) {
|
||||
setup();
|
||||
}
|
||||
|
||||
if (available) {
|
||||
return object;
|
||||
}
|
||||
|
||||
return null;
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,8 @@ package com.velocitypowered.natives.util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
|
||||
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
|
@ -8,29 +8,30 @@ import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.EventManager;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.proxy.command.ServerCommand;
|
||||
import com.velocitypowered.proxy.command.ShutdownCommand;
|
||||
import com.velocitypowered.proxy.command.VelocityCommand;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.network.http.NettyHttpClient;
|
||||
import com.velocitypowered.proxy.command.VelocityCommandManager;
|
||||
import com.velocitypowered.proxy.config.AnnotatedConfig;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.messages.VelocityChannelRegistrar;
|
||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||
import com.velocitypowered.proxy.network.http.NettyHttpClient;
|
||||
import com.velocitypowered.proxy.plugin.VelocityEventManager;
|
||||
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
|
||||
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
|
||||
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
|
||||
import com.velocitypowered.proxy.scheduler.VelocityScheduler;
|
||||
import com.velocitypowered.proxy.server.ServerMap;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.util.Ratelimiter;
|
||||
import com.velocitypowered.proxy.util.ServerMap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
@ -61,7 +62,7 @@ public class VelocityServer implements ProxyServer {
|
||||
private VelocityConfiguration configuration;
|
||||
private NettyHttpClient httpClient;
|
||||
private KeyPair serverKeyPair;
|
||||
private final ServerMap servers = new ServerMap();
|
||||
private final ServerMap servers = new ServerMap(this);
|
||||
private final VelocityCommandManager commandManager = new VelocityCommandManager();
|
||||
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
|
||||
private boolean shutdown = false;
|
||||
@ -263,19 +264,19 @@ public class VelocityServer implements ProxyServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerInfo> getServerInfo(String name) {
|
||||
public Optional<RegisteredServer> getServer(String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
return servers.getServer(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ServerInfo> getAllServers() {
|
||||
public Collection<RegisteredServer> getAllServers() {
|
||||
return servers.getAllServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerServer(ServerInfo server) {
|
||||
servers.register(server);
|
||||
public RegisteredServer registerServer(ServerInfo server) {
|
||||
return servers.register(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,6 +6,7 @@ import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.event.ClickEvent;
|
||||
@ -34,7 +35,7 @@ public class ServerCommand implements Command {
|
||||
if (args.length == 1) {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<ServerInfo> toConnect = server.getServerInfo(serverName);
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (!toConnect.isPresent()) {
|
||||
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
|
||||
return;
|
||||
@ -48,17 +49,19 @@ public class ServerCommand implements Command {
|
||||
|
||||
// Assemble the list of servers as components
|
||||
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
|
||||
List<ServerInfo> infos = ImmutableList.copyOf(server.getAllServers());
|
||||
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
|
||||
for (int i = 0; i < infos.size(); i++) {
|
||||
ServerInfo serverInfo = infos.get(i);
|
||||
TextComponent infoComponent = TextComponent.of(serverInfo.getName());
|
||||
if (serverInfo.getName().equals(currentServer)) {
|
||||
RegisteredServer rs = infos.get(i);
|
||||
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
|
||||
String playersText = rs.getPlayersConnected().size() + " player(s) online";
|
||||
if (rs.getServerInfo().getName().equals(currentServer)) {
|
||||
infoComponent = infoComponent.color(TextColor.GREEN)
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Currently connected to this server")));
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
TextComponent.of("Currently connected to this server\n" + playersText)));
|
||||
} else {
|
||||
infoComponent = infoComponent.color(TextColor.GRAY)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName()))
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server")));
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName()))
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
|
||||
}
|
||||
serverListBuilder.append(infoComponent);
|
||||
if (i != infos.size() - 1) {
|
||||
@ -74,11 +77,11 @@ public class ServerCommand implements Command {
|
||||
public List<String> suggest(CommandSource source, String[] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return server.getAllServers().stream()
|
||||
.map(ServerInfo::getName)
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.collect(Collectors.toList());
|
||||
} else if (currentArgs.length == 1) {
|
||||
return server.getAllServers().stream()
|
||||
.map(ServerInfo::getName)
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
|
@ -1,10 +1,9 @@
|
||||
package com.velocitypowered.proxy.command;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.CommandManager;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.ElementType;
|
||||
@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Only for simple configs
|
||||
|
@ -5,10 +5,9 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.api.util.LegacyChatColorUtils;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
@ -17,12 +16,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.util.*;
|
||||
|
||||
public class VelocityConfiguration extends AnnotatedConfig {
|
||||
|
||||
@ -273,6 +267,14 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return announceForge;
|
||||
}
|
||||
|
||||
public int getConnectTimeout() {
|
||||
return advanced.getConnectionTimeout();
|
||||
}
|
||||
|
||||
public int getReadTimeout() {
|
||||
return advanced.getReadTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@ -420,21 +422,23 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
"Disable by setting to 0"})
|
||||
@ConfigKey("login-ratelimit")
|
||||
private int loginRatelimit = 3000;
|
||||
@Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."})
|
||||
@ConfigKey("connection-timeout")
|
||||
private int connectionTimeout = 5000;
|
||||
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
|
||||
@ConfigKey("read-timeout")
|
||||
private int readTimeout = 30000;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
|
||||
private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) {
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.loginRatelimit = loginRatelimit;
|
||||
}
|
||||
|
||||
private Advanced(Toml toml) {
|
||||
if (toml != null) {
|
||||
this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue();
|
||||
this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
|
||||
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
|
||||
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
|
||||
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,33 +446,31 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return compressionThreshold;
|
||||
}
|
||||
|
||||
public void setCompressionThreshold(int compressionThreshold) {
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
}
|
||||
|
||||
public int getCompressionLevel() {
|
||||
return compressionLevel;
|
||||
}
|
||||
|
||||
public void setCompressionLevel(int compressionLevel) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
}
|
||||
|
||||
public int getLoginRatelimit() {
|
||||
return loginRatelimit;
|
||||
}
|
||||
|
||||
public void setLoginRatelimit(int loginRatelimit) {
|
||||
this.loginRatelimit = loginRatelimit;
|
||||
public int getConnectionTimeout() {
|
||||
return connectionTimeout;
|
||||
}
|
||||
|
||||
public int getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Advanced{"
|
||||
+ "compressionThreshold=" + compressionThreshold
|
||||
+ ", compressionLevel=" + compressionLevel
|
||||
+ ", loginRatelimit=" + loginRatelimit
|
||||
+ '}';
|
||||
return "Advanced{" +
|
||||
"compressionThreshold=" + compressionThreshold +
|
||||
", compressionLevel=" + compressionLevel +
|
||||
", loginRatelimit=" + loginRatelimit +
|
||||
", connectionTimeout=" + connectionTimeout +
|
||||
", readTimeout=" + readTimeout +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,18 +502,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return queryEnabled;
|
||||
}
|
||||
|
||||
public void setQueryEnabled(boolean queryEnabled) {
|
||||
this.queryEnabled = queryEnabled;
|
||||
}
|
||||
|
||||
public int getQueryPort() {
|
||||
return queryPort;
|
||||
}
|
||||
|
||||
public void setQueryPort(int queryPort) {
|
||||
this.queryPort = queryPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Query{"
|
||||
|
@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import com.velocitypowered.proxy.protocol.netty.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.CIPHER_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
/**
|
||||
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
|
||||
@ -99,8 +91,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
if (association != null) {
|
||||
logger.error("{}: exception encountered", association, cause);
|
||||
} else {
|
||||
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
|
||||
}
|
||||
|
||||
ctx.close();
|
||||
|
@ -8,6 +8,8 @@ public class VelocityConstants {
|
||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||
|
||||
public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS";
|
||||
public static final String FORGE_LEGACY_CHANNEL = "FML";
|
||||
public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP";
|
||||
|
||||
public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelSide;
|
||||
import com.velocitypowered.api.proxy.messages.MessageHandler;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo()));
|
||||
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer()));
|
||||
connection.getServer().addPlayer(connection.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (!connection.getPlayer().isActive()) {
|
||||
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
|
||||
// errors.
|
||||
connection.getMinecraftConnection().close();
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -44,7 +46,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
connection.getPlayer().getConnection().write(packet);
|
||||
} else if (packet instanceof Disconnect) {
|
||||
Disconnect original = (Disconnect) packet;
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original);
|
||||
connection.disconnect();
|
||||
connection.getPlayer().handleConnectionException(connection.getServer(), original);
|
||||
} else if (packet instanceof JoinGame) {
|
||||
playerHandler.handleBackendJoinGame((JoinGame) packet);
|
||||
} else if (packet instanceof BossBar) {
|
||||
@ -83,10 +86,17 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection,
|
||||
ChannelSide.FROM_SERVER, pm);
|
||||
if (status == MessageHandler.ForwardStatus.FORWARD) {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel());
|
||||
if (id == null) {
|
||||
connection.getPlayer().getConnection().write(pm);
|
||||
} else {
|
||||
PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), id, pm.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
connection.getPlayer().getConnection().write(pm);
|
||||
}
|
||||
}, connection.getMinecraftConnection().getChannel().eventLoop());
|
||||
}
|
||||
} else if (connection.hasCompletedJoin()) {
|
||||
// Just forward the packet on. We don't have anything to handle at this time.
|
||||
@ -99,7 +109,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (!connection.getPlayer().isActive()) {
|
||||
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
|
||||
// errors.
|
||||
connection.getMinecraftConnection().close();
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -110,7 +120,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable);
|
||||
connection.getPlayer().handleConnectionException(connection.getServer(), throwable);
|
||||
}
|
||||
|
||||
public VelocityServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
connection.getServer().removePlayer(connection.getPlayer());
|
||||
if (!connection.isGracefulDisconnect()) {
|
||||
connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create(
|
||||
ConnectionMessages.UNEXPECTED_DISCONNECT));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||
|
@ -1,18 +1,18 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.text.TextComponent;
|
||||
@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
if (existingConnection == null) {
|
||||
// Strap on the play session handler
|
||||
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer()));
|
||||
|
||||
// This is for legacy Forge servers - during first connection the FML handshake will transition to complete regardless
|
||||
// Thus, we need to ensure that a reset packet is ALWAYS sent on first switch.
|
||||
//
|
||||
// The call will handle if the player is not a Forge player appropriately.
|
||||
connection.getPlayer().getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
} else {
|
||||
// The previous server connection should become obsolete.
|
||||
// Before we remove it, if the server we are departing is modded, we must always reset the client state.
|
||||
|
@ -4,9 +4,14 @@ import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import io.netty.channel.*;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
@ -27,27 +31,22 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.HANDLER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||
import static com.velocitypowered.proxy.network.Connections.SERVER_READ_TIMEOUT_SECONDS;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
|
||||
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
|
||||
AttributeKey.newInstance("connection-notification-result");
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
private final VelocityRegisteredServer registeredServer;
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
private MinecraftConnection minecraftConnection;
|
||||
private boolean legacyForge = false;
|
||||
private boolean hasCompletedJoin = false;
|
||||
private boolean gracefulDisconnect = false;
|
||||
|
||||
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.serverInfo = target;
|
||||
public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.registeredServer = registeredServer;
|
||||
this.proxyPlayer = proxyPlayer;
|
||||
this.server = server;
|
||||
}
|
||||
@ -55,12 +54,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
|
||||
server.initializeGenericBootstrap()
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
|
||||
@ -73,7 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
ch.pipeline().addLast(HANDLER, connection);
|
||||
}
|
||||
})
|
||||
.connect(serverInfo.getAddress())
|
||||
.connect(registeredServer.getServerInfo().getAddress())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
@ -95,7 +93,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
|
||||
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
|
||||
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
|
||||
return serverInfo.getAddress().getHostString() + "\0" +
|
||||
return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getProfile().getId() + "\0" +
|
||||
GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||
@ -113,9 +111,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
} else if (proxyPlayer.getConnection().isLegacyForge()) {
|
||||
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
|
||||
} else {
|
||||
handshake.setServerAddress(serverInfo.getAddress().getHostString());
|
||||
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
|
||||
}
|
||||
handshake.setPort(serverInfo.getAddress().getPort());
|
||||
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
|
||||
minecraftConnection.write(handshake);
|
||||
|
||||
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
|
||||
@ -127,12 +125,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
minecraftConnection.write(login);
|
||||
}
|
||||
|
||||
public void writeIfJoined(PluginMessage message) {
|
||||
if (hasCompletedJoin) {
|
||||
minecraftConnection.write(message);
|
||||
}
|
||||
}
|
||||
|
||||
public MinecraftConnection getMinecraftConnection() {
|
||||
return minecraftConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityRegisteredServer getServer() {
|
||||
return registeredServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverInfo;
|
||||
return registeredServer.getServerInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -141,23 +151,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
if (minecraftConnection != null) {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
gracefulDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName();
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(identifier.getId());
|
||||
message.setData(data);
|
||||
minecraftConnection.write(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLegacyForge() {
|
||||
@ -175,4 +189,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
public void setHasCompletedJoin(boolean hasCompletedJoin) {
|
||||
this.hasCompletedJoin = hasCompletedJoin;
|
||||
}
|
||||
|
||||
public boolean isGracefulDisconnect() {
|
||||
return gracefulDisconnect;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelSide;
|
||||
import com.velocitypowered.api.proxy.messages.MessageHandler;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@ -33,6 +34,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private boolean spawned = false;
|
||||
private final List<UUID> serverBossBars = new ArrayList<>();
|
||||
private final Set<String> clientPluginMsgChannels = new HashSet<>();
|
||||
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
|
||||
private final VelocityServer server;
|
||||
|
||||
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
||||
@ -53,6 +55,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof KeepAlive) {
|
||||
KeepAlive keepAlive = (KeepAlive) packet;
|
||||
if (keepAlive.getRandomId() != lastPingID) {
|
||||
@ -62,7 +70,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
player.setPing(System.currentTimeMillis() - lastPingSent);
|
||||
resetPingData();
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -110,7 +118,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
player.getConnection().write(response);
|
||||
} else {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e);
|
||||
@ -125,15 +133,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
// If we don't want to handle this packet, just forward it on.
|
||||
if (player.getConnectedServer().hasCompletedJoin()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
if (serverConnection.hasCompletedJoin()) {
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
if (player.getConnectedServer().hasCompletedJoin()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(buf.retain());
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverConnection.hasCompletedJoin()) {
|
||||
serverConnection.getMinecraftConnection().write(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,11 +176,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
||||
resetPingData(); // reset ping data;
|
||||
resetPingData(); // reset ping data
|
||||
if (!spawned) {
|
||||
// nothing special to do here
|
||||
// Nothing special to do with regards to spawning the player
|
||||
spawned = true;
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
|
||||
// We have something special to do for legacy Forge servers - during first connection the FML handshake
|
||||
// will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on
|
||||
// first switch.
|
||||
//
|
||||
// As we know that calling this branch only happens on first join, we set that if we are a Forge
|
||||
// client that we must reset on the next switch.
|
||||
//
|
||||
// The call will handle if the player is not a Forge player appropriately.
|
||||
player.getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
} else {
|
||||
// Ah, this is the meat and potatoes of the whole venture!
|
||||
//
|
||||
@ -210,6 +234,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
channel, toRegister));
|
||||
}
|
||||
|
||||
// If we had plugin messages queued during login/FML handshake, send them now.
|
||||
PluginMessage pm;
|
||||
while ((pm = loginPluginMessages.poll()) != null) {
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
|
||||
}
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
player.getConnectedServer().getMinecraftConnection().flush();
|
||||
@ -234,8 +264,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return serverBossBars;
|
||||
}
|
||||
|
||||
public void handleClientPluginMessage(PluginMessage packet) {
|
||||
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) {
|
||||
private void handleClientPluginMessage(PluginMessage packet) {
|
||||
if (PluginMessageUtil.isMCRegister(packet)) {
|
||||
List<String> actuallyRegistered = new ArrayList<>();
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
for (String channel : channels) {
|
||||
@ -252,31 +282,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
|
||||
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
|
||||
} else if (PluginMessageUtil.isMCUnregister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
clientPluginMsgChannels.removeAll(channels);
|
||||
}
|
||||
|
||||
if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
||||
// Ensure that the messages are forwarded
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player,
|
||||
ChannelSide.FROM_CLIENT, packet);
|
||||
if (status == MessageHandler.ForwardStatus.FORWARD) {
|
||||
// We're going to forward on the original packet.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
||||
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
// Always forward the FML handshake to the remote server.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else {
|
||||
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
|
||||
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
|
||||
// be sent once the JoinGame packet has been received by the proxy.
|
||||
loginPluginMessages.add(packet);
|
||||
}
|
||||
} else {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else {
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
}
|
||||
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.player.SkinParts;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ClientSettingsWrapper implements PlayerSettings {
|
||||
|
@ -7,28 +7,24 @@ import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
import com.velocitypowered.api.permission.PermissionProvider;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
@ -159,8 +155,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) {
|
||||
return new ConnectionRequestBuilderImpl(info);
|
||||
public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
|
||||
return new ConnectionRequestBuilderImpl(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -195,52 +191,57 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return connectedServer;
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Throwable throwable) {
|
||||
public void handleConnectionException(RegisteredServer server, Throwable throwable) {
|
||||
String error = ThrowableUtils.briefDescription(throwable);
|
||||
String userMessage;
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
userMessage = "Exception in server " + info.getName();
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||
userMessage = "Exception in server " + server.getServerInfo().getName();
|
||||
} else {
|
||||
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + info.getName();
|
||||
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + server.getServerInfo().getName();
|
||||
}
|
||||
handleConnectionException(info, null, TextComponent.builder()
|
||||
handleConnectionException(server, null, TextComponent.builder()
|
||||
.content(userMessage + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(TextComponent.of(error, TextColor.WHITE))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Disconnect disconnect) {
|
||||
public void handleConnectionException(RegisteredServer server, Disconnect disconnect) {
|
||||
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason);
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||
logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason);
|
||||
handleConnectionException(server, disconnectReason, TextComponent.builder()
|
||||
.content("Kicked from " + server.getServerInfo().getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
} else {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason);
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason);
|
||||
handleConnectionException(server, disconnectReason, TextComponent.builder()
|
||||
.content("Unable to connect to " + server.getServerInfo().getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
}
|
||||
handleConnectionException(info, disconnectReason, TextComponent.builder()
|
||||
.content("Unable to connect to " + info.getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) {
|
||||
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);;
|
||||
private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) {
|
||||
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo());
|
||||
connectionInFlight = null;
|
||||
if (connectedServer == null) {
|
||||
// The player isn't yet connected to a server.
|
||||
Optional<ServerInfo> nextServer = getNextServerToTry();
|
||||
Optional<RegisteredServer> nextServer = getNextServerToTry();
|
||||
if (nextServer.isPresent()) {
|
||||
createConnectionRequest(nextServer.get()).fireAndForget();
|
||||
} else {
|
||||
connection.closeWith(Disconnect.create(friendlyReason));
|
||||
}
|
||||
} else if (connectedServer.getServerInfo().equals(info)) {
|
||||
} else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) {
|
||||
// Already connected to the server being disconnected from.
|
||||
if (kickReason != null) {
|
||||
server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason))
|
||||
server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
|
||||
KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult();
|
||||
@ -261,7 +262,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
}
|
||||
|
||||
Optional<ServerInfo> getNextServerToTry() {
|
||||
Optional<RegisteredServer> getNextServerToTry() {
|
||||
List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder();
|
||||
if (tryIndex >= serversToTry.size()) {
|
||||
return Optional.empty();
|
||||
@ -272,21 +273,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return server.getServers().getServer(toTryName);
|
||||
}
|
||||
|
||||
private CompletableFuture<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) {
|
||||
return CompletableFuture.completedFuture(
|
||||
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS)
|
||||
);
|
||||
return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS);
|
||||
}
|
||||
if (connectedServer != null && connectedServer.getServer().equals(server)) {
|
||||
return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) {
|
||||
return CompletableFuture.completedFuture(
|
||||
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED)
|
||||
);
|
||||
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
|
||||
Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(request.getServer());
|
||||
if (initialCheck.isPresent()) {
|
||||
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get()));
|
||||
}
|
||||
|
||||
// Otherwise, initiate the connection.
|
||||
ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer()));
|
||||
ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
|
||||
return server.getEventManager().fire(event)
|
||||
.thenCompose((newEvent) -> {
|
||||
if (!newEvent.getResult().isAllowed()) {
|
||||
@ -295,7 +300,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
);
|
||||
}
|
||||
|
||||
return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect();
|
||||
RegisteredServer rs = newEvent.getResult().getServer().get();
|
||||
Optional<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
|
||||
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(identifier.getId());
|
||||
message.setData(data);
|
||||
connection.write(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
private final ServerInfo info;
|
||||
private final RegisteredServer server;
|
||||
|
||||
ConnectionRequestBuilderImpl(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(info, "info");
|
||||
ConnectionRequestBuilderImpl(RegisteredServer server) {
|
||||
this.server = Preconditions.checkNotNull(server, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServer() {
|
||||
return info;
|
||||
public RegisteredServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -372,7 +383,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
connect()
|
||||
.whenCompleteAsync((status, throwable) -> {
|
||||
if (throwable != null) {
|
||||
handleConnectionException(info, throwable);
|
||||
handleConnectionException(server, throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -387,7 +398,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
// Ignored; the plugin probably already handled this.
|
||||
break;
|
||||
case SERVER_DISCONNECTED:
|
||||
handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
|
||||
handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
|
||||
break;
|
||||
}
|
||||
}, connection.getChannel().eventLoop());
|
||||
|
@ -5,12 +5,12 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
|
||||
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
||||
private final ConnectedPlayer player;
|
||||
|
@ -2,22 +2,23 @@ package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
@ -39,6 +40,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||
private static final String MOJANG_SERVER_AUTH_URL =
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
|
||||
@ -157,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) {
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
@ -218,7 +220,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
private void handleProxyLogin(ConnectedPlayer player) {
|
||||
Optional<ServerInfo> toTry = player.getNextServerToTry();
|
||||
Optional<RegisteredServer> toTry = player.getNextServerToTry();
|
||||
if (!toTry.isPresent()) {
|
||||
player.close(TextComponent.of("No available servers", TextColor.RED));
|
||||
return;
|
||||
@ -244,7 +246,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4,16 +4,16 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
|
@ -7,6 +7,7 @@ public class ConnectionMessages {
|
||||
public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED);
|
||||
public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED);
|
||||
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error");
|
||||
public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?");
|
||||
|
||||
private ConnectionMessages() {
|
||||
throw new AssertionError();
|
||||
|
@ -4,7 +4,9 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import net.minecrell.terminalconsole.SimpleTerminalConsole;
|
||||
import org.jline.reader.*;
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
@ -2,10 +2,10 @@ package com.velocitypowered.proxy.messages;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.messages.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class);
|
||||
private final Map<String, MessageHandler> handlers = new ConcurrentHashMap<>();
|
||||
private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void register(MessageHandler handler, ChannelIdentifier... identifiers) {
|
||||
public void register(ChannelIdentifier... identifiers) {
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier,
|
||||
"identifier is unknown");
|
||||
}
|
||||
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
handlers.put(identifier.getId(), handler);
|
||||
identifierMap.put(identifier.getId(), identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) {
|
||||
MessageHandler handler = handlers.get(message.getChannel());
|
||||
ChannelIdentifier identifier = identifierMap.get(message.getChannel());
|
||||
if (handler == null || identifier == null) {
|
||||
return MessageHandler.ForwardStatus.FORWARD;
|
||||
}
|
||||
|
||||
try {
|
||||
return handler.handle(source, side, identifier, message.getData());
|
||||
} catch (Exception e) {
|
||||
logger.info("Unable to handle plugin message on channel {} for {}", message.getChannel(), source);
|
||||
// In case of doubt, do not forward the message on.
|
||||
return MessageHandler.ForwardStatus.HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ChannelIdentifier... identifiers) {
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
}
|
||||
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
handlers.remove(identifier.getId());
|
||||
identifierMap.remove(identifier.getId());
|
||||
}
|
||||
}
|
||||
@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
public boolean registered(String id) {
|
||||
return identifierMap.containsKey(id);
|
||||
}
|
||||
|
||||
public ChannelIdentifier getFromId(String id) {
|
||||
return identifierMap.get(id);
|
||||
}
|
||||
}
|
||||
|
@ -7,21 +7,11 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
|
||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.*;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.epoll.*;
|
||||
import io.netty.channel.kqueue.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
@ -75,7 +65,7 @@ public final class ConnectionManager {
|
||||
@Override
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
|
||||
@ -125,7 +115,9 @@ public final class ConnectionManager {
|
||||
public Bootstrap createWorker() {
|
||||
return new Bootstrap()
|
||||
.channel(this.transportType.socketChannelClass)
|
||||
.group(this.workerGroup);
|
||||
.group(this.workerGroup)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
@ -13,7 +13,4 @@ public interface Connections {
|
||||
String MINECRAFT_DECODER = "minecraft-decoder";
|
||||
String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||
String READ_TIMEOUT = "read-timeout";
|
||||
|
||||
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
|
||||
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package com.velocitypowered.proxy.network.http;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.pool.*;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
@ -13,7 +13,10 @@ import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory;
|
||||
import net.kyori.event.EventSubscriber;
|
||||
import net.kyori.event.PostResult;
|
||||
import net.kyori.event.SimpleEventBus;
|
||||
import net.kyori.event.method.*;
|
||||
import net.kyori.event.method.EventExecutor;
|
||||
import net.kyori.event.method.MethodScanner;
|
||||
import net.kyori.event.method.MethodSubscriptionAdapter;
|
||||
import net.kyori.event.method.SimpleMethodSubscriptionAdapter;
|
||||
import net.kyori.event.method.asm.ASMEventExecutorFactory;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VelocityEventManager implements EventManager {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.plugin;
|
||||
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
|
@ -2,7 +2,9 @@ package com.velocitypowered.proxy.plugin.loader;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.velocitypowered.api.plugin.*;
|
||||
import com.velocitypowered.api.plugin.InvalidPluginException;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
|
@ -8,7 +8,6 @@ import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -8,7 +8,7 @@ import io.netty.util.collection.IntObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
|
||||
@ -23,7 +23,7 @@ public enum StateRegistry {
|
||||
},
|
||||
STATUS {
|
||||
{
|
||||
SERVERBOUND.register(StatusRequest.class, StatusRequest::new,
|
||||
SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
|
||||
genericMappings(0x00));
|
||||
SERVERBOUND.register(StatusPing.class, StatusPing::new,
|
||||
genericMappings(0x01));
|
||||
|
@ -101,9 +101,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
queryResponse.writeByte(QUERY_TYPE_STAT);
|
||||
queryResponse.writeInt(sessionId);
|
||||
|
||||
// Fetch information
|
||||
Collection<Player> players = server.getAllPlayers();
|
||||
|
||||
// Start writing the response
|
||||
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
|
||||
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
|
||||
@ -114,12 +111,14 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
responseWriter.write("plugins", "");
|
||||
|
||||
responseWriter.write("map", "Velocity");
|
||||
responseWriter.write("numplayers", players.size());
|
||||
responseWriter.write("numplayers", server.getPlayerCount());
|
||||
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
|
||||
responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
|
||||
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
|
||||
|
||||
responseWriter.writePlayers(players);
|
||||
if (!responseWriter.isBasic) {
|
||||
responseWriter.writePlayers(server.getAllPlayers());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -132,6 +131,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
ctx.writeAndFlush(responsePacket);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
|
||||
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
|
||||
// count.
|
||||
responsePacket.release();
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
@ -1,7 +1,10 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.*;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
@ -4,7 +4,6 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@ -2,13 +2,13 @@ package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializer;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
|
||||
|
||||
public class HeaderAndFooter implements MinecraftPacket {
|
||||
|
||||
private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}");
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@ -1,10 +1,16 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusRequest implements MinecraftPacket {
|
||||
public static final StatusRequest INSTANCE = new StatusRequest();
|
||||
|
||||
private StatusRequest() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
|
||||
@ -14,4 +20,9 @@ public class StatusRequest implements MinecraftPacket {
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StatusRequest";
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -20,13 +19,20 @@ public enum PluginMessageUtil {
|
||||
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand");
|
||||
}
|
||||
|
||||
public static boolean isMCRegister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register");
|
||||
}
|
||||
|
||||
public static boolean isMCUnregister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister");
|
||||
}
|
||||
|
||||
public static List<String> getChannels(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(message.getChannel().equals("REGISTER") ||
|
||||
message.getChannel().equals("UNREGISTER") ||
|
||||
message.getChannel().equals("minecraft:register") ||
|
||||
message.getChannel().equals("minecraft:unregister"),
|
||||
"Unknown channel type " + message.getChannel());
|
||||
Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s",
|
||||
message.getChannel());
|
||||
String channels = new String(message.getData(), StandardCharsets.UTF_8);
|
||||
return ImmutableList.copyOf(channels.split("\0"));
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class VelocityScheduler implements Scheduler {
|
||||
private final PluginManager pluginManager;
|
||||
@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder delay(int time, TimeUnit unit) {
|
||||
public TaskBuilder delay(long time, TimeUnit unit) {
|
||||
this.delay = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder repeat(int time, TimeUnit unit) {
|
||||
public TaskBuilder repeat(long time, TimeUnit unit) {
|
||||
this.repeat = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
@ -91,62 +90,9 @@ public class VelocityScheduler implements Scheduler {
|
||||
|
||||
@Override
|
||||
public ScheduledTask schedule() {
|
||||
if (delay == 0 && repeat == 0) {
|
||||
// A special purpose, simplified implementation
|
||||
VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
taskService.execute(task);
|
||||
return task;
|
||||
} else {
|
||||
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VelocityImmediatelyScheduledTask implements ScheduledTask, Runnable {
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private final AtomicReference<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);
|
||||
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +100,7 @@ public class VelocityScheduler implements Scheduler {
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private ScheduledFuture<?> future;
|
||||
private volatile Thread currentTaskThread;
|
||||
|
||||
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
|
||||
this.plugin = plugin;
|
||||
@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler {
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
|
||||
Thread cur = currentTaskThread;
|
||||
if (cur != null) {
|
||||
cur.interrupt();
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler {
|
||||
@Override
|
||||
public void run() {
|
||||
taskService.execute(() -> {
|
||||
currentTaskThread = Thread.currentThread();
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler {
|
||||
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
|
||||
}
|
||||
}
|
||||
currentTaskThread = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
49
proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java
Normale Datei
49
proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java
Normale Datei
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import com.google.common.collect.MapMaker;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
|
@ -3,9 +3,7 @@ package com.velocitypowered.proxy.protocol;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PacketRegistryTest {
|
||||
|
@ -9,7 +9,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class VelocitySchedulerTest {
|
||||
// TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep()
|
||||
|
@ -7,7 +7,8 @@ import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RatelimiterTest {
|
||||
|
||||
|
@ -1,31 +1,34 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.server.ServerMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ServerMapTest {
|
||||
private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565);
|
||||
|
||||
@Test
|
||||
void respectsCaseInsensitivity() {
|
||||
ServerMap map = new ServerMap();
|
||||
ServerMap map = new ServerMap(null);
|
||||
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
|
||||
map.register(info);
|
||||
RegisteredServer connection = map.register(info);
|
||||
|
||||
assertEquals(Optional.of(info), map.getServer("TestServer"));
|
||||
assertEquals(Optional.of(info), map.getServer("testserver"));
|
||||
assertEquals(Optional.of(info), map.getServer("TESTSERVER"));
|
||||
assertEquals(Optional.of(connection), map.getServer("TestServer"));
|
||||
assertEquals(Optional.of(connection), map.getServer("testserver"));
|
||||
assertEquals(Optional.of(connection), map.getServer("TESTSERVER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsRepeatedRegisterAttempts() {
|
||||
ServerMap map = new ServerMap();
|
||||
ServerMap map = new ServerMap(null);
|
||||
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
|
||||
map.register(info);
|
||||
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren